@gajae-code/ai 0.1.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.
Files changed (349) hide show
  1. package/CHANGELOG.md +2644 -0
  2. package/README.md +1181 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +641 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +49 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +71 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +237 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +36 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +188 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  39. package/dist/types/providers/cursor.d.ts +42 -0
  40. package/dist/types/providers/error-message.d.ts +27 -0
  41. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  42. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  43. package/dist/types/providers/google-auth.d.ts +24 -0
  44. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  45. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  46. package/dist/types/providers/google-shared.d.ts +163 -0
  47. package/dist/types/providers/google-types.d.ts +138 -0
  48. package/dist/types/providers/google-vertex.d.ts +7 -0
  49. package/dist/types/providers/google.d.ts +4 -0
  50. package/dist/types/providers/grammar.d.ts +1 -0
  51. package/dist/types/providers/kimi.d.ts +27 -0
  52. package/dist/types/providers/mock.d.ts +175 -0
  53. package/dist/types/providers/ollama.d.ts +6 -0
  54. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  55. package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
  56. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  57. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  58. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  59. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  60. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  61. package/dist/types/providers/openai-completions-compat.d.ts +25 -0
  62. package/dist/types/providers/openai-completions.d.ts +33 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +12 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +24 -0
  75. package/dist/types/types.d.ts +746 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/kimi.d.ts +2 -0
  81. package/dist/types/usage/minimax-code.d.ts +2 -0
  82. package/dist/types/usage/openai-codex.d.ts +3 -0
  83. package/dist/types/usage/shared.d.ts +1 -0
  84. package/dist/types/usage/zai.d.ts +2 -0
  85. package/dist/types/usage.d.ts +258 -0
  86. package/dist/types/utils/abort.d.ts +19 -0
  87. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  88. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  89. package/dist/types/utils/discovery/codex.d.ts +38 -0
  90. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  91. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  92. package/dist/types/utils/discovery/index.d.ts +4 -0
  93. package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
  94. package/dist/types/utils/event-stream.d.ts +28 -0
  95. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  96. package/dist/types/utils/foundry.d.ts +1 -0
  97. package/dist/types/utils/h2-fetch.d.ts +22 -0
  98. package/dist/types/utils/http-inspector.d.ts +31 -0
  99. package/dist/types/utils/idle-iterator.d.ts +67 -0
  100. package/dist/types/utils/json-parse.d.ts +10 -0
  101. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  102. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  103. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  104. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  105. package/dist/types/utils/oauth/callback-server.d.ts +57 -0
  106. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  107. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  108. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  109. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  110. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  111. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  112. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  113. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  114. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  115. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  116. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  117. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  118. package/dist/types/utils/oauth/index.d.ts +38 -0
  119. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  120. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  121. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  122. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  123. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  124. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  125. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  126. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  127. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  128. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  129. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  130. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  131. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  132. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  133. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  134. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  135. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  136. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  137. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  138. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  139. package/dist/types/utils/oauth/together.d.ts +1 -0
  140. package/dist/types/utils/oauth/types.d.ts +44 -0
  141. package/dist/types/utils/oauth/venice.d.ts +18 -0
  142. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  143. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  144. package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
  145. package/dist/types/utils/oauth/zai.d.ts +18 -0
  146. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  147. package/dist/types/utils/overflow.d.ts +54 -0
  148. package/dist/types/utils/parse-bind.d.ts +23 -0
  149. package/dist/types/utils/provider-response.d.ts +3 -0
  150. package/dist/types/utils/retry-after.d.ts +3 -0
  151. package/dist/types/utils/retry.d.ts +26 -0
  152. package/dist/types/utils/schema/adapt.d.ts +24 -0
  153. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  154. package/dist/types/utils/schema/dereference.d.ts +11 -0
  155. package/dist/types/utils/schema/draft.d.ts +10 -0
  156. package/dist/types/utils/schema/equality.d.ts +4 -0
  157. package/dist/types/utils/schema/fields.d.ts +49 -0
  158. package/dist/types/utils/schema/index.d.ts +13 -0
  159. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  160. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  161. package/dist/types/utils/schema/normalize.d.ts +93 -0
  162. package/dist/types/utils/schema/spill.d.ts +8 -0
  163. package/dist/types/utils/schema/stamps.d.ts +25 -0
  164. package/dist/types/utils/schema/types.d.ts +4 -0
  165. package/dist/types/utils/schema/wire.d.ts +54 -0
  166. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  167. package/dist/types/utils/sse-debug.d.ts +10 -0
  168. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  169. package/dist/types/utils/tool-choice.d.ts +50 -0
  170. package/dist/types/utils/validation.d.ts +17 -0
  171. package/dist/types/utils.d.ts +28 -0
  172. package/package.json +146 -0
  173. package/src/api-registry.ts +96 -0
  174. package/src/auth-broker/client.ts +358 -0
  175. package/src/auth-broker/index.ts +5 -0
  176. package/src/auth-broker/refresher.ts +127 -0
  177. package/src/auth-broker/remote-store.ts +623 -0
  178. package/src/auth-broker/server.ts +644 -0
  179. package/src/auth-broker/types.ts +127 -0
  180. package/src/auth-broker/wire-schemas.ts +200 -0
  181. package/src/auth-gateway/http.ts +194 -0
  182. package/src/auth-gateway/index.ts +3 -0
  183. package/src/auth-gateway/server.ts +717 -0
  184. package/src/auth-gateway/types.ts +134 -0
  185. package/src/auth-storage.ts +4104 -0
  186. package/src/cli.ts +262 -0
  187. package/src/index.ts +54 -0
  188. package/src/model-cache.ts +129 -0
  189. package/src/model-manager.ts +450 -0
  190. package/src/model-thinking.ts +691 -0
  191. package/src/models.json +73853 -0
  192. package/src/models.json.d.ts +9 -0
  193. package/src/models.ts +56 -0
  194. package/src/prompts/turn-aborted-guidance.md +4 -0
  195. package/src/provider-details.ts +90 -0
  196. package/src/provider-models/bundled-references.ts +38 -0
  197. package/src/provider-models/descriptors.ts +308 -0
  198. package/src/provider-models/google.ts +91 -0
  199. package/src/provider-models/index.ts +5 -0
  200. package/src/provider-models/ollama.ts +153 -0
  201. package/src/provider-models/openai-compat.ts +2275 -0
  202. package/src/provider-models/special.ts +67 -0
  203. package/src/providers/amazon-bedrock.ts +849 -0
  204. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  205. package/src/providers/anthropic-messages-server.ts +677 -0
  206. package/src/providers/anthropic.ts +2696 -0
  207. package/src/providers/aws-credentials.ts +501 -0
  208. package/src/providers/aws-eventstream.ts +185 -0
  209. package/src/providers/aws-sigv4.ts +218 -0
  210. package/src/providers/azure-openai-responses.ts +337 -0
  211. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  212. package/src/providers/cursor/proto/agent.proto +3526 -0
  213. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  214. package/src/providers/cursor/proto/buf.yaml +17 -0
  215. package/src/providers/cursor.ts +2561 -0
  216. package/src/providers/error-message.ts +21 -0
  217. package/src/providers/github-copilot-headers.ts +140 -0
  218. package/src/providers/gitlab-duo.ts +372 -0
  219. package/src/providers/google-auth.ts +252 -0
  220. package/src/providers/google-gemini-cli.ts +795 -0
  221. package/src/providers/google-gemini-headers.ts +41 -0
  222. package/src/providers/google-shared.ts +902 -0
  223. package/src/providers/google-types.ts +167 -0
  224. package/src/providers/google-vertex.ts +88 -0
  225. package/src/providers/google.ts +41 -0
  226. package/src/providers/grammar.ts +70 -0
  227. package/src/providers/kimi.ts +52 -0
  228. package/src/providers/mock.ts +500 -0
  229. package/src/providers/ollama.ts +544 -0
  230. package/src/providers/openai-anthropic-shim.ts +138 -0
  231. package/src/providers/openai-chat-server-schema.ts +243 -0
  232. package/src/providers/openai-chat-server.ts +628 -0
  233. package/src/providers/openai-codex/constants.ts +43 -0
  234. package/src/providers/openai-codex/request-transformer.ts +161 -0
  235. package/src/providers/openai-codex/response-handler.ts +81 -0
  236. package/src/providers/openai-codex-responses.ts +2598 -0
  237. package/src/providers/openai-completions-compat.ts +279 -0
  238. package/src/providers/openai-completions.ts +1853 -0
  239. package/src/providers/openai-responses-server-schema.ts +290 -0
  240. package/src/providers/openai-responses-server.ts +1183 -0
  241. package/src/providers/openai-responses-shared.ts +800 -0
  242. package/src/providers/openai-responses.ts +621 -0
  243. package/src/providers/pi-native-client.ts +228 -0
  244. package/src/providers/pi-native-server.ts +210 -0
  245. package/src/providers/register-builtins.ts +412 -0
  246. package/src/providers/synthetic.ts +50 -0
  247. package/src/providers/transform-messages.ts +309 -0
  248. package/src/providers/vision-guard.ts +31 -0
  249. package/src/rate-limit-utils.ts +84 -0
  250. package/src/stream.ts +895 -0
  251. package/src/types.ts +884 -0
  252. package/src/usage/claude.ts +431 -0
  253. package/src/usage/gemini.ts +250 -0
  254. package/src/usage/github-copilot.ts +421 -0
  255. package/src/usage/google-antigravity.ts +201 -0
  256. package/src/usage/kimi.ts +271 -0
  257. package/src/usage/minimax-code.ts +31 -0
  258. package/src/usage/openai-codex.ts +503 -0
  259. package/src/usage/shared.ts +10 -0
  260. package/src/usage/zai.ts +247 -0
  261. package/src/usage.ts +183 -0
  262. package/src/utils/abort.ts +51 -0
  263. package/src/utils/anthropic-auth.ts +87 -0
  264. package/src/utils/discovery/antigravity.ts +261 -0
  265. package/src/utils/discovery/codex.ts +371 -0
  266. package/src/utils/discovery/cursor.ts +306 -0
  267. package/src/utils/discovery/gemini.ts +248 -0
  268. package/src/utils/discovery/index.ts +4 -0
  269. package/src/utils/discovery/openai-compatible.ts +224 -0
  270. package/src/utils/event-stream.ts +142 -0
  271. package/src/utils/fireworks-model-id.ts +30 -0
  272. package/src/utils/foundry.ts +8 -0
  273. package/src/utils/h2-fetch.ts +60 -0
  274. package/src/utils/http-inspector.ts +176 -0
  275. package/src/utils/idle-iterator.ts +250 -0
  276. package/src/utils/json-parse.ts +148 -0
  277. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  278. package/src/utils/oauth/anthropic.ts +200 -0
  279. package/src/utils/oauth/api-key-login.ts +87 -0
  280. package/src/utils/oauth/api-key-validation.ts +92 -0
  281. package/src/utils/oauth/callback-server.ts +276 -0
  282. package/src/utils/oauth/cerebras.ts +16 -0
  283. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  284. package/src/utils/oauth/cursor.ts +157 -0
  285. package/src/utils/oauth/deepseek.ts +53 -0
  286. package/src/utils/oauth/firepass.ts +24 -0
  287. package/src/utils/oauth/fireworks.ts +15 -0
  288. package/src/utils/oauth/github-copilot.ts +362 -0
  289. package/src/utils/oauth/gitlab-duo.ts +123 -0
  290. package/src/utils/oauth/google-antigravity.ts +200 -0
  291. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  292. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  293. package/src/utils/oauth/huggingface.ts +62 -0
  294. package/src/utils/oauth/index.ts +444 -0
  295. package/src/utils/oauth/kagi.ts +47 -0
  296. package/src/utils/oauth/kilo.ts +87 -0
  297. package/src/utils/oauth/kimi.ts +254 -0
  298. package/src/utils/oauth/litellm.ts +47 -0
  299. package/src/utils/oauth/lm-studio.ts +38 -0
  300. package/src/utils/oauth/minimax-code.ts +78 -0
  301. package/src/utils/oauth/moonshot.ts +16 -0
  302. package/src/utils/oauth/nanogpt.ts +15 -0
  303. package/src/utils/oauth/nvidia.ts +70 -0
  304. package/src/utils/oauth/oauth.html +199 -0
  305. package/src/utils/oauth/ollama-cloud.ts +28 -0
  306. package/src/utils/oauth/ollama.ts +47 -0
  307. package/src/utils/oauth/openai-codex.ts +299 -0
  308. package/src/utils/oauth/opencode.ts +49 -0
  309. package/src/utils/oauth/parallel.ts +46 -0
  310. package/src/utils/oauth/perplexity.ts +206 -0
  311. package/src/utils/oauth/pkce.ts +18 -0
  312. package/src/utils/oauth/qianfan.ts +58 -0
  313. package/src/utils/oauth/qwen-portal.ts +60 -0
  314. package/src/utils/oauth/synthetic.ts +16 -0
  315. package/src/utils/oauth/tavily.ts +46 -0
  316. package/src/utils/oauth/together.ts +16 -0
  317. package/src/utils/oauth/types.ts +94 -0
  318. package/src/utils/oauth/venice.ts +59 -0
  319. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  320. package/src/utils/oauth/vllm.ts +40 -0
  321. package/src/utils/oauth/xiaomi.ts +137 -0
  322. package/src/utils/oauth/zai.ts +60 -0
  323. package/src/utils/oauth/zenmux.ts +15 -0
  324. package/src/utils/overflow.ts +137 -0
  325. package/src/utils/parse-bind.ts +54 -0
  326. package/src/utils/provider-response.ts +30 -0
  327. package/src/utils/retry-after.ts +110 -0
  328. package/src/utils/retry.ts +54 -0
  329. package/src/utils/schema/CONSTRAINTS.md +164 -0
  330. package/src/utils/schema/adapt.ts +36 -0
  331. package/src/utils/schema/compatibility.ts +435 -0
  332. package/src/utils/schema/dereference.ts +98 -0
  333. package/src/utils/schema/draft.ts +341 -0
  334. package/src/utils/schema/equality.ts +97 -0
  335. package/src/utils/schema/fields.ts +190 -0
  336. package/src/utils/schema/index.ts +13 -0
  337. package/src/utils/schema/json-schema-validator.ts +577 -0
  338. package/src/utils/schema/meta-validator.ts +167 -0
  339. package/src/utils/schema/normalize.ts +1588 -0
  340. package/src/utils/schema/spill.ts +43 -0
  341. package/src/utils/schema/stamps.ts +97 -0
  342. package/src/utils/schema/types.ts +11 -0
  343. package/src/utils/schema/wire.ts +213 -0
  344. package/src/utils/schema/zod-decontaminate.ts +331 -0
  345. package/src/utils/sse-debug.ts +289 -0
  346. package/src/utils/tool-call-healing.ts +271 -0
  347. package/src/utils/tool-choice.ts +99 -0
  348. package/src/utils/validation.ts +1019 -0
  349. package/src/utils.ts +166 -0
@@ -0,0 +1,431 @@
1
+ import { scheduler } from "node:timers/promises";
2
+ import type {
3
+ CredentialRankingStrategy,
4
+ UsageAmount,
5
+ UsageFetchContext,
6
+ UsageFetchParams,
7
+ UsageLimit,
8
+ UsageProvider,
9
+ UsageReport,
10
+ UsageStatus,
11
+ UsageWindow,
12
+ } from "../usage";
13
+ import { isRecord, toNumber } from "../utils";
14
+
15
+ const DEFAULT_ENDPOINT = "https://api.anthropic.com/api/oauth";
16
+ const FIVE_HOURS_MS = 5 * 60 * 60 * 1000;
17
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
18
+ const MAX_ATTEMPTS = 3;
19
+ const BASE_RETRY_DELAY_MS = 500;
20
+
21
+ const CLAUDE_HEADERS = {
22
+ accept: "application/json, text/plain, */*",
23
+ "accept-encoding": "gzip, compress, deflate, br",
24
+ "anthropic-beta":
25
+ "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05",
26
+ "content-type": "application/json",
27
+ "user-agent": "claude-cli/2.1.63 (external, cli)",
28
+ connection: "keep-alive",
29
+ } as const;
30
+
31
+ function normalizeClaudeBaseUrl(baseUrl?: string): string {
32
+ if (!baseUrl?.trim()) return DEFAULT_ENDPOINT;
33
+ const trimmed = baseUrl.trim().replace(/\/+$/, "");
34
+ const lower = trimmed.toLowerCase();
35
+ if (lower.endsWith("/api/oauth")) return trimmed;
36
+ let url: URL;
37
+ try {
38
+ url = new URL(trimmed);
39
+ } catch {
40
+ return DEFAULT_ENDPOINT;
41
+ }
42
+ let path = url.pathname.replace(/\/+$/, "");
43
+ if (path === "/") path = "";
44
+ if (path.toLowerCase().endsWith("/v1")) {
45
+ path = path.slice(0, -3);
46
+ }
47
+ if (!path) return `${url.origin}/api/oauth`;
48
+ return `${url.origin}${path}/api/oauth`;
49
+ }
50
+
51
+ interface ClaudeUsageBucket {
52
+ utilization?: number;
53
+ resets_at?: string;
54
+ }
55
+
56
+ interface ParsedUsageBucket {
57
+ utilization?: number;
58
+ resetsAt?: number;
59
+ }
60
+
61
+ interface ClaudeUsageResponse {
62
+ five_hour?: ClaudeUsageBucket | null;
63
+ seven_day?: ClaudeUsageBucket | null;
64
+ seven_day_opus?: ClaudeUsageBucket | null;
65
+ seven_day_sonnet?: ClaudeUsageBucket | null;
66
+ }
67
+
68
+ type ClaudeUsagePayload = {
69
+ payload: ClaudeUsageResponse;
70
+ orgId?: string;
71
+ };
72
+
73
+ function parseIsoTime(value: string | undefined): number | undefined {
74
+ if (!value) return undefined;
75
+ const parsed = Date.parse(value);
76
+ return Number.isFinite(parsed) ? parsed : undefined;
77
+ }
78
+
79
+ function parseBucket(bucket: unknown): ParsedUsageBucket | undefined {
80
+ if (!isRecord(bucket)) return undefined;
81
+ const utilization = toNumber(bucket.utilization);
82
+ const resetsAt = parseIsoTime(typeof bucket.resets_at === "string" ? bucket.resets_at : undefined);
83
+ if (utilization === undefined && resetsAt === undefined) {
84
+ return undefined;
85
+ }
86
+ return { utilization, resetsAt };
87
+ }
88
+
89
+ function getPayloadString(payload: Record<string, unknown>, key: string): string | undefined {
90
+ const value = payload[key];
91
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
92
+ }
93
+
94
+ function getNestedPayloadString(payload: Record<string, unknown>, key: string, nestedKey: string): string | undefined {
95
+ const nested = payload[key];
96
+ return isRecord(nested) ? getPayloadString(nested, nestedKey) : undefined;
97
+ }
98
+
99
+ function extractUsageIdentity(payload: ClaudeUsageResponse, orgId?: string): { accountId?: string; email?: string } {
100
+ if (!isRecord(payload)) return { accountId: orgId };
101
+ const accountId =
102
+ getPayloadString(payload, "account_id") ??
103
+ getPayloadString(payload, "accountId") ??
104
+ getPayloadString(payload, "user_id") ??
105
+ getPayloadString(payload, "userId") ??
106
+ getPayloadString(payload, "org_id") ??
107
+ getPayloadString(payload, "orgId") ??
108
+ getNestedPayloadString(payload, "account", "uuid") ??
109
+ getNestedPayloadString(payload, "account", "id") ??
110
+ getNestedPayloadString(payload, "organization", "uuid") ??
111
+ getNestedPayloadString(payload, "organization", "id") ??
112
+ getNestedPayloadString(payload, "user", "uuid") ??
113
+ getNestedPayloadString(payload, "user", "id") ??
114
+ orgId;
115
+ const email =
116
+ getPayloadString(payload, "email") ??
117
+ getPayloadString(payload, "user_email") ??
118
+ getPayloadString(payload, "userEmail") ??
119
+ getNestedPayloadString(payload, "account", "email") ??
120
+ getNestedPayloadString(payload, "user", "email");
121
+ return { accountId, email };
122
+ }
123
+
124
+ function hasUsageData(payload: ClaudeUsageResponse): boolean {
125
+ return (
126
+ parseBucket(payload.five_hour)?.utilization !== undefined ||
127
+ parseBucket(payload.seven_day)?.utilization !== undefined ||
128
+ parseBucket(payload.seven_day_opus)?.utilization !== undefined ||
129
+ parseBucket(payload.seven_day_sonnet)?.utilization !== undefined
130
+ );
131
+ }
132
+
133
+ function isRetryableStatus(status: number): boolean {
134
+ return status === 429 || (status >= 500 && status < 600);
135
+ }
136
+
137
+ function isAbortError(error: unknown, signal?: AbortSignal): boolean {
138
+ if (signal?.aborted) return true;
139
+ if (!isRecord(error)) return false;
140
+ return error.name === "AbortError" || error.name === "TimeoutError";
141
+ }
142
+
143
+ function retryDelayMs(attempt: number, retryAfter: string | null): number {
144
+ const baseline = BASE_RETRY_DELAY_MS * 2 ** attempt;
145
+ if (!retryAfter?.trim()) return baseline;
146
+ const seconds = Number.parseFloat(retryAfter);
147
+ if (Number.isFinite(seconds)) return Math.max(baseline, Math.max(0, seconds * 1000));
148
+ const dateDelay = Date.parse(retryAfter) - Date.now();
149
+ return Number.isFinite(dateDelay) ? Math.max(baseline, Math.max(0, dateDelay)) : baseline;
150
+ }
151
+
152
+ async function waitBeforeRetry(
153
+ attempt: number,
154
+ retryAfter: string | null,
155
+ signal?: AbortSignal,
156
+ retryWait?: UsageFetchContext["retryWait"],
157
+ ): Promise<boolean> {
158
+ if (signal?.aborted) return false;
159
+ if (attempt >= MAX_ATTEMPTS - 1) return false;
160
+ try {
161
+ const delayMs = retryDelayMs(attempt, retryAfter);
162
+ if (retryWait) {
163
+ await retryWait(delayMs, signal);
164
+ } else {
165
+ await scheduler.wait(delayMs, { signal });
166
+ }
167
+ return !signal?.aborted;
168
+ } catch (error) {
169
+ if (isAbortError(error, signal)) return false;
170
+ throw error;
171
+ }
172
+ }
173
+
174
+ async function fetchUsagePayload(
175
+ url: string,
176
+ headers: Record<string, string>,
177
+ ctx: UsageFetchContext,
178
+ signal?: AbortSignal,
179
+ ): Promise<ClaudeUsagePayload | null> {
180
+ if (signal?.aborted) return null;
181
+
182
+ let lastPayload: ClaudeUsageResponse | null = null;
183
+ let lastOrgId: string | undefined;
184
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
185
+ try {
186
+ const response = await ctx.fetch(url, { headers, signal });
187
+ const orgId = response.headers.get("anthropic-organization-id")?.trim() || undefined;
188
+ lastOrgId = orgId ?? lastOrgId;
189
+
190
+ if (!response.ok) {
191
+ const retryable = isRetryableStatus(response.status);
192
+ ctx.logger?.warn("Claude usage fetch failed", {
193
+ status: response.status,
194
+ statusText: response.statusText,
195
+ attempt,
196
+ willRetry: retryable && attempt < MAX_ATTEMPTS - 1,
197
+ });
198
+ if (!retryable) return null;
199
+ const retryAfter = response.headers.get("retry-after");
200
+ if (!(await waitBeforeRetry(attempt, retryAfter, signal, ctx.retryWait))) break;
201
+ continue;
202
+ }
203
+
204
+ const parsed = (await response.json()) as unknown;
205
+ if (isRecord(parsed)) {
206
+ const payload = parsed as ClaudeUsageResponse;
207
+ lastPayload = payload;
208
+ if (hasUsageData(payload)) return { payload, orgId };
209
+ }
210
+
211
+ ctx.logger?.warn("Claude usage response missing usage data", {
212
+ attempt,
213
+ willRetry: attempt < MAX_ATTEMPTS - 1,
214
+ });
215
+ if (!(await waitBeforeRetry(attempt, null, signal, ctx.retryWait))) break;
216
+ } catch (error) {
217
+ if (isAbortError(error, signal)) return null;
218
+ ctx.logger?.warn("Claude usage fetch error", {
219
+ error: String(error),
220
+ attempt,
221
+ willRetry: attempt < MAX_ATTEMPTS - 1,
222
+ });
223
+ if (!(await waitBeforeRetry(attempt, null, signal, ctx.retryWait))) break;
224
+ }
225
+ }
226
+
227
+ return lastPayload ? { payload: lastPayload, orgId: lastOrgId } : null;
228
+ }
229
+
230
+ interface ClaudeProfile {
231
+ uuid?: string;
232
+ email?: string;
233
+ account?: {
234
+ uuid?: string;
235
+ email?: string;
236
+ };
237
+ }
238
+
239
+ function extractProfileIdentity(profile: ClaudeProfile | null): { accountId?: string; email?: string } {
240
+ if (!profile || !isRecord(profile)) return {};
241
+ const account = isRecord(profile.account) ? profile.account : undefined;
242
+ return {
243
+ accountId:
244
+ (typeof profile.uuid === "string" && profile.uuid.trim() ? profile.uuid.trim() : undefined) ??
245
+ (typeof account?.uuid === "string" && account.uuid.trim() ? account.uuid.trim() : undefined),
246
+ email:
247
+ (typeof profile.email === "string" && profile.email.trim() ? profile.email.trim() : undefined) ??
248
+ (typeof account?.email === "string" && account.email.trim() ? account.email.trim() : undefined),
249
+ };
250
+ }
251
+
252
+ async function fetchProfile(
253
+ baseUrl: string,
254
+ headers: Record<string, string>,
255
+ ctx: UsageFetchContext,
256
+ signal?: AbortSignal,
257
+ ): Promise<ClaudeProfile | null> {
258
+ if (signal?.aborted) return null;
259
+ const url = `${baseUrl}/profile`;
260
+ try {
261
+ const response = await ctx.fetch(url, { headers, signal });
262
+ if (!response.ok) return null;
263
+ const payload = (await response.json()) as unknown;
264
+ return isRecord(payload) ? (payload as ClaudeProfile) : null;
265
+ } catch (error) {
266
+ if (isAbortError(error, signal)) return null;
267
+ ctx.logger?.debug("Claude profile fetch error", { error: String(error) });
268
+ return null;
269
+ }
270
+ }
271
+
272
+ function buildUsageAmount(utilization: number | undefined): UsageAmount | undefined {
273
+ if (utilization === undefined) return undefined;
274
+ const clamped = Math.min(Math.max(utilization, 0), 100);
275
+ const usedFraction = clamped / 100;
276
+ return {
277
+ used: clamped,
278
+ limit: 100,
279
+ remaining: Math.max(0, 100 - clamped),
280
+ usedFraction,
281
+ remainingFraction: Math.max(0, 1 - usedFraction),
282
+ unit: "percent",
283
+ };
284
+ }
285
+
286
+ function buildUsageStatus(usedFraction: number | undefined): UsageStatus | undefined {
287
+ if (usedFraction === undefined) return undefined;
288
+ if (usedFraction >= 1) return "exhausted";
289
+ if (usedFraction >= 0.9) return "warning";
290
+ return "ok";
291
+ }
292
+
293
+ function buildUsageLimit(args: {
294
+ id: string;
295
+ label: string;
296
+ windowId: string;
297
+ windowLabel: string;
298
+ durationMs: number;
299
+ bucket: ParsedUsageBucket | undefined;
300
+ provider: "anthropic";
301
+ tier?: string;
302
+ shared?: boolean;
303
+ }): UsageLimit | null {
304
+ if (!args.bucket) return null;
305
+ const amount = buildUsageAmount(args.bucket.utilization);
306
+ if (!amount) return null;
307
+ const window: UsageWindow = {
308
+ id: args.windowId,
309
+ label: args.windowLabel,
310
+ durationMs: args.durationMs,
311
+ ...(args.bucket.resetsAt !== undefined ? { resetsAt: args.bucket.resetsAt } : {}),
312
+ };
313
+ return {
314
+ id: args.id,
315
+ label: args.label,
316
+ scope: {
317
+ provider: args.provider,
318
+ windowId: args.windowId,
319
+ tier: args.tier,
320
+ shared: args.shared,
321
+ },
322
+ window,
323
+ amount,
324
+ status: buildUsageStatus(amount.usedFraction),
325
+ };
326
+ }
327
+
328
+ async function fetchClaudeUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise<UsageReport | null> {
329
+ if (params.provider !== "anthropic") return null;
330
+ const credential = params.credential;
331
+ if (credential.type !== "oauth" || !credential.accessToken) return null;
332
+
333
+ const baseUrl = normalizeClaudeBaseUrl(params.baseUrl);
334
+ const url = `${baseUrl}/usage`;
335
+ const headers: Record<string, string> = {
336
+ ...CLAUDE_HEADERS,
337
+ authorization: `Bearer ${credential.accessToken}`,
338
+ };
339
+
340
+ const payloadResult = await fetchUsagePayload(url, headers, ctx, params.signal);
341
+ if (!payloadResult || !isRecord(payloadResult.payload)) return null;
342
+ const { payload, orgId } = payloadResult;
343
+
344
+ const fiveHour = parseBucket(payload.five_hour);
345
+ const sevenDay = parseBucket(payload.seven_day);
346
+ const sevenDayOpus = parseBucket(payload.seven_day_opus);
347
+ const sevenDaySonnet = parseBucket(payload.seven_day_sonnet);
348
+
349
+ const limits = [
350
+ buildUsageLimit({
351
+ id: "anthropic:5h",
352
+ label: "Claude 5 Hour",
353
+ windowId: "5h",
354
+ windowLabel: "5 Hour",
355
+ durationMs: FIVE_HOURS_MS,
356
+ bucket: fiveHour,
357
+ provider: "anthropic",
358
+ shared: true,
359
+ }),
360
+ buildUsageLimit({
361
+ id: "anthropic:7d",
362
+ label: "Claude 7 Day",
363
+ windowId: "7d",
364
+ windowLabel: "7 Day",
365
+ durationMs: SEVEN_DAYS_MS,
366
+ bucket: sevenDay,
367
+ provider: "anthropic",
368
+ shared: true,
369
+ }),
370
+ buildUsageLimit({
371
+ id: "anthropic:7d:opus",
372
+ label: "Claude 7 Day (Opus)",
373
+ windowId: "7d",
374
+ windowLabel: "7 Day",
375
+ durationMs: SEVEN_DAYS_MS,
376
+ bucket: sevenDayOpus,
377
+ provider: "anthropic",
378
+ tier: "opus",
379
+ }),
380
+ buildUsageLimit({
381
+ id: "anthropic:7d:sonnet",
382
+ label: "Claude 7 Day (Sonnet)",
383
+ windowId: "7d",
384
+ windowLabel: "7 Day",
385
+ durationMs: SEVEN_DAYS_MS,
386
+ bucket: sevenDaySonnet,
387
+ provider: "anthropic",
388
+ tier: "sonnet",
389
+ }),
390
+ ].filter((limit): limit is UsageLimit => limit !== null);
391
+
392
+ if (limits.length === 0) return null;
393
+ const identity = extractUsageIdentity(payload, orgId);
394
+ let accountId = identity.accountId ?? credential.accountId;
395
+ let email = identity.email ?? credential.email;
396
+ if ((!accountId || !email) && !params.signal?.aborted) {
397
+ const profileIdentity = extractProfileIdentity(await fetchProfile(baseUrl, headers, ctx, params.signal));
398
+ accountId = accountId ?? profileIdentity.accountId;
399
+ email = email ?? profileIdentity.email;
400
+ }
401
+
402
+ const report: UsageReport = {
403
+ provider: params.provider,
404
+ fetchedAt: Date.now(),
405
+ limits,
406
+ metadata: {
407
+ endpoint: url,
408
+ ...(accountId ? { accountId } : {}),
409
+ ...(email ? { email } : {}),
410
+ ...(orgId ? { orgId } : {}),
411
+ },
412
+ raw: payload,
413
+ };
414
+
415
+ return report;
416
+ }
417
+
418
+ export const claudeUsageProvider: UsageProvider = {
419
+ id: "anthropic",
420
+ fetchUsage: fetchClaudeUsage,
421
+ supports: params => params.provider === "anthropic" && params.credential.type === "oauth",
422
+ };
423
+
424
+ export const claudeRankingStrategy: CredentialRankingStrategy = {
425
+ findWindowLimits(report) {
426
+ const primary = report.limits.find(l => l.id === "anthropic:5h");
427
+ const secondary = report.limits.find(l => l.id === "anthropic:7d");
428
+ return { primary, secondary };
429
+ },
430
+ windowDefaults: { primaryMs: 5 * 60 * 60 * 1000, secondaryMs: 7 * 24 * 60 * 60 * 1000 },
431
+ };
@@ -0,0 +1,250 @@
1
+ import { getGeminiCliHeaders } from "../providers/google-gemini-cli";
2
+ import type {
3
+ UsageAmount,
4
+ UsageFetchContext,
5
+ UsageFetchParams,
6
+ UsageLimit,
7
+ UsageProvider,
8
+ UsageReport,
9
+ UsageWindow,
10
+ } from "../usage";
11
+
12
+ // (Refresh is the sole responsibility of AuthStorage; no provider-direct refresh here.)
13
+
14
+ const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
15
+
16
+ const GEMINI_TIER_MAP: Array<{ tier: string; models: string[] }> = [
17
+ {
18
+ tier: "3-Flash",
19
+ models: ["gemini-3-flash-preview", "gemini-3-flash"],
20
+ },
21
+ {
22
+ tier: "Flash",
23
+ models: ["gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-2.0-flash", "gemini-1.5-flash"],
24
+ },
25
+ {
26
+ tier: "Pro",
27
+ models: ["gemini-2.5-pro", "gemini-3-pro-preview", "gemini-3.1-pro-preview", "gemini-3-pro", "gemini-1.5-pro"],
28
+ },
29
+ ];
30
+
31
+ interface LoadCodeAssistResponse {
32
+ cloudaicompanionProject?: string | { id?: string };
33
+ currentTier?: { id?: string; name?: string };
34
+ }
35
+
36
+ interface RetrieveUserQuotaResponse {
37
+ buckets?: Array<{
38
+ modelId?: string;
39
+ remainingFraction?: number;
40
+ resetTime?: string;
41
+ }>;
42
+ }
43
+
44
+ function getProjectId(payload: LoadCodeAssistResponse | undefined): string | undefined {
45
+ if (!payload) return undefined;
46
+ if (typeof payload.cloudaicompanionProject === "string") {
47
+ return payload.cloudaicompanionProject;
48
+ }
49
+ if (payload.cloudaicompanionProject && typeof payload.cloudaicompanionProject === "object") {
50
+ return payload.cloudaicompanionProject.id;
51
+ }
52
+ return undefined;
53
+ }
54
+
55
+ function getModelTier(modelId: string): string | undefined {
56
+ for (const entry of GEMINI_TIER_MAP) {
57
+ if (entry.models.includes(modelId)) {
58
+ return entry.tier;
59
+ }
60
+ }
61
+ const normalized = modelId.toLowerCase();
62
+ if (normalized.includes("flash")) return "Flash";
63
+ if (normalized.includes("pro")) return "Pro";
64
+ return undefined;
65
+ }
66
+
67
+ function parseWindow(resetTime: string | undefined): UsageWindow {
68
+ if (!resetTime) {
69
+ return {
70
+ id: "quota",
71
+ label: "Quota window",
72
+ };
73
+ }
74
+ const resetsAt = Date.parse(resetTime);
75
+ if (Number.isNaN(resetsAt)) {
76
+ return {
77
+ id: "quota",
78
+ label: "Quota window",
79
+ };
80
+ }
81
+ return {
82
+ id: `reset-${resetsAt}`,
83
+ label: "Quota window",
84
+ resetsAt,
85
+ };
86
+ }
87
+
88
+ function buildAmount(remainingFraction: number | undefined): UsageAmount {
89
+ if (remainingFraction === undefined || !Number.isFinite(remainingFraction)) {
90
+ return { unit: "percent" };
91
+ }
92
+ const remaining = Math.min(Math.max(remainingFraction, 0), 1);
93
+ const used = Math.min(Math.max(1 - remaining, 0), 1);
94
+ return {
95
+ unit: "percent",
96
+ used: Math.round(used * 1000) / 10,
97
+ remaining: Math.round(remaining * 1000) / 10,
98
+ limit: 100,
99
+ usedFraction: used,
100
+ remainingFraction: remaining,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Return the OAuth access token to use against `/v1internal:*`. AuthStorage is
106
+ * the sole refresh authority (broker-aware, single-flighted, rotation-safe);
107
+ * if the token landed here expired or near-expired, the next usage cycle will
108
+ * carry a freshly-refreshed credential. Returning `undefined` short-circuits
109
+ * the probe rather than POSTing a stale token to Google.
110
+ */
111
+ function resolveAccessToken(params: UsageFetchParams): string | undefined {
112
+ const { credential } = params;
113
+ if (credential.type !== "oauth") return undefined;
114
+ if (!credential.accessToken) return undefined;
115
+ if (credential.expiresAt !== undefined && credential.expiresAt <= Date.now()) {
116
+ return undefined;
117
+ }
118
+ return credential.accessToken;
119
+ }
120
+
121
+ async function loadCodeAssist(
122
+ params: UsageFetchParams,
123
+ ctx: UsageFetchContext,
124
+ accessToken: string,
125
+ baseUrl: string,
126
+ projectId?: string,
127
+ ): Promise<LoadCodeAssistResponse | undefined> {
128
+ const response = await ctx.fetch(`${baseUrl}/v1internal:loadCodeAssist`, {
129
+ method: "POST",
130
+ headers: {
131
+ Authorization: `Bearer ${accessToken}`,
132
+ "Content-Type": "application/json",
133
+ ...getGeminiCliHeaders(),
134
+ },
135
+ body: JSON.stringify({
136
+ ...(projectId ? { cloudaicompanionProject: projectId } : {}),
137
+ metadata: {
138
+ ideType: "IDE_UNSPECIFIED",
139
+ platform: "PLATFORM_UNSPECIFIED",
140
+ pluginType: "GEMINI",
141
+ },
142
+ }),
143
+ signal: params.signal,
144
+ });
145
+
146
+ if (!response.ok) {
147
+ const errorText = await response.text();
148
+ ctx.logger?.warn("Gemini CLI loadCodeAssist failed", {
149
+ status: response.status,
150
+ error: errorText,
151
+ });
152
+ return undefined;
153
+ }
154
+
155
+ return (await response.json()) as LoadCodeAssistResponse;
156
+ }
157
+
158
+ async function fetchQuota(
159
+ params: UsageFetchParams,
160
+ ctx: UsageFetchContext,
161
+ accessToken: string,
162
+ baseUrl: string,
163
+ projectId?: string,
164
+ ): Promise<RetrieveUserQuotaResponse | undefined> {
165
+ const response = await ctx.fetch(`${baseUrl}/v1internal:retrieveUserQuota`, {
166
+ method: "POST",
167
+ headers: {
168
+ Authorization: `Bearer ${accessToken}`,
169
+ "Content-Type": "application/json",
170
+ ...getGeminiCliHeaders(),
171
+ },
172
+ body: JSON.stringify(projectId ? { project: projectId } : {}),
173
+ signal: params.signal,
174
+ });
175
+
176
+ if (!response.ok) {
177
+ const errorText = await response.text();
178
+ ctx.logger?.warn("Gemini CLI retrieveUserQuota failed", {
179
+ status: response.status,
180
+ error: errorText,
181
+ });
182
+ return undefined;
183
+ }
184
+
185
+ return (await response.json()) as RetrieveUserQuotaResponse;
186
+ }
187
+
188
+ export const googleGeminiCliUsageProvider: UsageProvider = {
189
+ id: "google-gemini-cli",
190
+ supports: ({ credential }) => credential.type === "oauth" && !!credential.accessToken,
191
+ async fetchUsage(params, ctx) {
192
+ const { credential } = params;
193
+ if (credential.type !== "oauth") {
194
+ return null;
195
+ }
196
+ const accessToken = resolveAccessToken(params);
197
+ if (!accessToken) {
198
+ return null;
199
+ }
200
+
201
+ const baseUrl = (params.baseUrl?.trim() || DEFAULT_ENDPOINT).replace(/\/$/, "");
202
+
203
+ const loadResponse = await loadCodeAssist(params, ctx, accessToken, baseUrl, credential.projectId);
204
+ const projectId = credential.projectId ?? getProjectId(loadResponse);
205
+ const quotaResponse = await fetchQuota(params, ctx, accessToken, baseUrl, projectId);
206
+ if (!quotaResponse) {
207
+ return null;
208
+ }
209
+
210
+ const limits: UsageLimit[] = [];
211
+ const buckets = quotaResponse.buckets ?? [];
212
+
213
+ buckets.forEach((bucket, index) => {
214
+ const modelId = bucket.modelId;
215
+ const window = parseWindow(bucket.resetTime);
216
+ const amount = buildAmount(bucket.remainingFraction);
217
+ const tier = modelId ? getModelTier(modelId) : undefined;
218
+ const label = modelId ? `Gemini ${modelId}` : "Gemini quota";
219
+ const id = `${modelId ?? "unknown"}:${window?.id ?? index}`;
220
+
221
+ limits.push({
222
+ id,
223
+ label,
224
+ scope: {
225
+ provider: params.provider,
226
+ accountId: credential.accountId,
227
+ projectId,
228
+ modelId,
229
+ tier,
230
+ windowId: window?.id,
231
+ },
232
+ window,
233
+ amount,
234
+ });
235
+ });
236
+
237
+ const report: UsageReport = {
238
+ provider: params.provider,
239
+ fetchedAt: Date.now(),
240
+ limits,
241
+ metadata: {
242
+ currentTierId: loadResponse?.currentTier?.id,
243
+ currentTierName: loadResponse?.currentTier?.name,
244
+ },
245
+ raw: quotaResponse,
246
+ };
247
+
248
+ return report;
249
+ },
250
+ };