@slkiser/opencode-quota 4.0.0 → 4.1.0

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 (266) hide show
  1. package/README.md +37 -25
  2. package/dist/bin/opencode-quota.d.ts.map +1 -1
  3. package/dist/bin/opencode-quota.js +8 -0
  4. package/dist/bin/opencode-quota.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/lib/anthropic.d.ts.map +1 -1
  8. package/dist/lib/anthropic.js +78 -72
  9. package/dist/lib/anthropic.js.map +1 -1
  10. package/dist/lib/api-key-resolver.d.ts.map +1 -1
  11. package/dist/lib/api-key-resolver.js +14 -24
  12. package/dist/lib/api-key-resolver.js.map +1 -1
  13. package/dist/lib/atomic-json.d.ts +3 -1
  14. package/dist/lib/atomic-json.d.ts.map +1 -1
  15. package/dist/lib/atomic-json.js +23 -9
  16. package/dist/lib/atomic-json.js.map +1 -1
  17. package/dist/lib/chutes.d.ts.map +1 -1
  18. package/dist/lib/chutes.js +28 -23
  19. package/dist/lib/chutes.js.map +1 -1
  20. package/dist/lib/cli-show.d.ts +9 -0
  21. package/dist/lib/cli-show.d.ts.map +1 -1
  22. package/dist/lib/cli-show.js +34 -5
  23. package/dist/lib/cli-show.js.map +1 -1
  24. package/dist/lib/cli-status.d.ts +8 -0
  25. package/dist/lib/cli-status.d.ts.map +1 -0
  26. package/dist/lib/cli-status.js +125 -0
  27. package/dist/lib/cli-status.js.map +1 -0
  28. package/dist/lib/config.d.ts +1 -1
  29. package/dist/lib/config.d.ts.map +1 -1
  30. package/dist/lib/config.js +47 -12
  31. package/dist/lib/config.js.map +1 -1
  32. package/dist/lib/copilot.d.ts +11 -5
  33. package/dist/lib/copilot.d.ts.map +1 -1
  34. package/dist/lib/copilot.js +300 -52
  35. package/dist/lib/copilot.js.map +1 -1
  36. package/dist/lib/deepseek.d.ts.map +1 -1
  37. package/dist/lib/deepseek.js +22 -17
  38. package/dist/lib/deepseek.js.map +1 -1
  39. package/dist/lib/display-sanitize.d.ts.map +1 -1
  40. package/dist/lib/display-sanitize.js +8 -0
  41. package/dist/lib/display-sanitize.js.map +1 -1
  42. package/dist/lib/entries.d.ts +14 -1
  43. package/dist/lib/entries.d.ts.map +1 -1
  44. package/dist/lib/entries.js.map +1 -1
  45. package/dist/lib/format-utils.d.ts +7 -0
  46. package/dist/lib/format-utils.d.ts.map +1 -1
  47. package/dist/lib/format-utils.js +18 -0
  48. package/dist/lib/format-utils.js.map +1 -1
  49. package/dist/lib/format.d.ts +1 -0
  50. package/dist/lib/format.d.ts.map +1 -1
  51. package/dist/lib/format.js +22 -7
  52. package/dist/lib/format.js.map +1 -1
  53. package/dist/lib/google-agy.d.ts.map +1 -1
  54. package/dist/lib/google-agy.js +203 -92
  55. package/dist/lib/google-agy.js.map +1 -1
  56. package/dist/lib/google-gemini-cli.d.ts.map +1 -1
  57. package/dist/lib/google-gemini-cli.js +53 -41
  58. package/dist/lib/google-gemini-cli.js.map +1 -1
  59. package/dist/lib/google-token-cache.d.ts.map +1 -1
  60. package/dist/lib/google-token-cache.js +87 -34
  61. package/dist/lib/google-token-cache.js.map +1 -1
  62. package/dist/lib/google.d.ts.map +1 -1
  63. package/dist/lib/google.js +57 -39
  64. package/dist/lib/google.js.map +1 -1
  65. package/dist/lib/grouped-entry-normalization.d.ts.map +1 -1
  66. package/dist/lib/grouped-entry-normalization.js +7 -1
  67. package/dist/lib/grouped-entry-normalization.js.map +1 -1
  68. package/dist/lib/http.d.ts +12 -7
  69. package/dist/lib/http.d.ts.map +1 -1
  70. package/dist/lib/http.js +28 -15
  71. package/dist/lib/http.js.map +1 -1
  72. package/dist/lib/init-installer.d.ts +2 -1
  73. package/dist/lib/init-installer.d.ts.map +1 -1
  74. package/dist/lib/init-installer.js +29 -2
  75. package/dist/lib/init-installer.js.map +1 -1
  76. package/dist/lib/kimi.d.ts.map +1 -1
  77. package/dist/lib/kimi.js +20 -15
  78. package/dist/lib/kimi.js.map +1 -1
  79. package/dist/lib/maintainer-announcements.d.ts.map +1 -1
  80. package/dist/lib/maintainer-announcements.js +10 -13
  81. package/dist/lib/maintainer-announcements.js.map +1 -1
  82. package/dist/lib/mimo-config.d.ts +29 -0
  83. package/dist/lib/mimo-config.d.ts.map +1 -0
  84. package/dist/lib/mimo-config.js +147 -0
  85. package/dist/lib/mimo-config.js.map +1 -0
  86. package/dist/lib/mimo.d.ts +35 -0
  87. package/dist/lib/mimo.d.ts.map +1 -0
  88. package/dist/lib/mimo.js +236 -0
  89. package/dist/lib/mimo.js.map +1 -0
  90. package/dist/lib/modelsdev-pricing.d.ts.map +1 -1
  91. package/dist/lib/modelsdev-pricing.js +39 -27
  92. package/dist/lib/modelsdev-pricing.js.map +1 -1
  93. package/dist/lib/nanogpt.d.ts.map +1 -1
  94. package/dist/lib/nanogpt.js +40 -30
  95. package/dist/lib/nanogpt.js.map +1 -1
  96. package/dist/lib/ollama-cloud.d.ts +1 -1
  97. package/dist/lib/ollama-cloud.d.ts.map +1 -1
  98. package/dist/lib/ollama-cloud.js +73 -68
  99. package/dist/lib/ollama-cloud.js.map +1 -1
  100. package/dist/lib/openai.d.ts.map +1 -1
  101. package/dist/lib/openai.js +65 -45
  102. package/dist/lib/openai.js.map +1 -1
  103. package/dist/lib/opencode-config-editor.d.ts +6 -0
  104. package/dist/lib/opencode-config-editor.d.ts.map +1 -1
  105. package/dist/lib/opencode-config-editor.js +47 -1
  106. package/dist/lib/opencode-config-editor.js.map +1 -1
  107. package/dist/lib/opencode-config-providers.d.ts.map +1 -1
  108. package/dist/lib/opencode-config-providers.js +13 -16
  109. package/dist/lib/opencode-config-providers.js.map +1 -1
  110. package/dist/lib/opencode-config-read.d.ts +23 -0
  111. package/dist/lib/opencode-config-read.d.ts.map +1 -0
  112. package/dist/lib/opencode-config-read.js +30 -0
  113. package/dist/lib/opencode-config-read.js.map +1 -0
  114. package/dist/lib/opencode-go.d.ts.map +1 -1
  115. package/dist/lib/opencode-go.js +44 -39
  116. package/dist/lib/opencode-go.js.map +1 -1
  117. package/dist/lib/opencode-zen-config.d.ts +34 -0
  118. package/dist/lib/opencode-zen-config.d.ts.map +1 -0
  119. package/dist/lib/opencode-zen-config.js +103 -0
  120. package/dist/lib/opencode-zen-config.js.map +1 -0
  121. package/dist/lib/opencode-zen.d.ts +27 -0
  122. package/dist/lib/opencode-zen.d.ts.map +1 -0
  123. package/dist/lib/opencode-zen.js +143 -0
  124. package/dist/lib/opencode-zen.js.map +1 -0
  125. package/dist/lib/provider-add-command.d.ts.map +1 -1
  126. package/dist/lib/provider-add-command.js +21 -2
  127. package/dist/lib/provider-add-command.js.map +1 -1
  128. package/dist/lib/provider-add-json-v1-questionnaire.d.ts +32 -0
  129. package/dist/lib/provider-add-json-v1-questionnaire.d.ts.map +1 -0
  130. package/dist/lib/provider-add-json-v1-questionnaire.js +336 -0
  131. package/dist/lib/provider-add-json-v1-questionnaire.js.map +1 -0
  132. package/dist/lib/provider-add.d.ts.map +1 -1
  133. package/dist/lib/provider-add.js +11 -2
  134. package/dist/lib/provider-add.js.map +1 -1
  135. package/dist/lib/provider-availability.d.ts +2 -2
  136. package/dist/lib/provider-availability.d.ts.map +1 -1
  137. package/dist/lib/provider-availability.js +2 -3
  138. package/dist/lib/provider-availability.js.map +1 -1
  139. package/dist/lib/provider-metadata.d.ts +289 -1
  140. package/dist/lib/provider-metadata.d.ts.map +1 -1
  141. package/dist/lib/provider-metadata.js +321 -227
  142. package/dist/lib/provider-metadata.js.map +1 -1
  143. package/dist/lib/quota-command-format.d.ts.map +1 -1
  144. package/dist/lib/quota-command-format.js +12 -16
  145. package/dist/lib/quota-command-format.js.map +1 -1
  146. package/dist/lib/quota-dialog-commands.d.ts +59 -2
  147. package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
  148. package/dist/lib/quota-dialog-commands.js +71 -30
  149. package/dist/lib/quota-dialog-commands.js.map +1 -1
  150. package/dist/lib/quota-entry-display.d.ts +2 -0
  151. package/dist/lib/quota-entry-display.d.ts.map +1 -1
  152. package/dist/lib/quota-entry-display.js +27 -13
  153. package/dist/lib/quota-entry-display.js.map +1 -1
  154. package/dist/lib/quota-providers-remote.d.ts +6 -1
  155. package/dist/lib/quota-providers-remote.d.ts.map +1 -1
  156. package/dist/lib/quota-providers-remote.js +376 -12
  157. package/dist/lib/quota-providers-remote.js.map +1 -1
  158. package/dist/lib/quota-providers.d.ts +84 -3
  159. package/dist/lib/quota-providers.d.ts.map +1 -1
  160. package/dist/lib/quota-providers.js +561 -24
  161. package/dist/lib/quota-providers.js.map +1 -1
  162. package/dist/lib/quota-render-data.d.ts +4 -0
  163. package/dist/lib/quota-render-data.d.ts.map +1 -1
  164. package/dist/lib/quota-render-data.js +101 -121
  165. package/dist/lib/quota-render-data.js.map +1 -1
  166. package/dist/lib/quota-runtime-context.d.ts +9 -1
  167. package/dist/lib/quota-runtime-context.d.ts.map +1 -1
  168. package/dist/lib/quota-runtime-context.js +5 -0
  169. package/dist/lib/quota-runtime-context.js.map +1 -1
  170. package/dist/lib/quota-state.d.ts.map +1 -1
  171. package/dist/lib/quota-state.js +26 -2
  172. package/dist/lib/quota-state.js.map +1 -1
  173. package/dist/lib/quota-stats.d.ts +1 -0
  174. package/dist/lib/quota-stats.d.ts.map +1 -1
  175. package/dist/lib/quota-stats.js +14 -4
  176. package/dist/lib/quota-stats.js.map +1 -1
  177. package/dist/lib/quota-status.d.ts.map +1 -1
  178. package/dist/lib/quota-status.js +96 -1
  179. package/dist/lib/quota-status.js.map +1 -1
  180. package/dist/lib/runtime-provider-ids.d.ts +4 -0
  181. package/dist/lib/runtime-provider-ids.d.ts.map +1 -0
  182. package/dist/lib/runtime-provider-ids.js +10 -0
  183. package/dist/lib/runtime-provider-ids.js.map +1 -0
  184. package/dist/lib/session-tokens.d.ts +2 -0
  185. package/dist/lib/session-tokens.d.ts.map +1 -1
  186. package/dist/lib/session-tokens.js +4 -2
  187. package/dist/lib/session-tokens.js.map +1 -1
  188. package/dist/lib/synthetic.d.ts.map +1 -1
  189. package/dist/lib/synthetic.js +37 -32
  190. package/dist/lib/synthetic.js.map +1 -1
  191. package/dist/lib/toast-format-grouped.d.ts +1 -0
  192. package/dist/lib/toast-format-grouped.d.ts.map +1 -1
  193. package/dist/lib/toast-format-grouped.js +33 -29
  194. package/dist/lib/toast-format-grouped.js.map +1 -1
  195. package/dist/lib/tui-refresh-lifecycle.d.ts +17 -0
  196. package/dist/lib/tui-refresh-lifecycle.d.ts.map +1 -0
  197. package/dist/lib/tui-refresh-lifecycle.js +83 -0
  198. package/dist/lib/tui-refresh-lifecycle.js.map +1 -0
  199. package/dist/lib/tui-runtime.d.ts +0 -5
  200. package/dist/lib/tui-runtime.d.ts.map +1 -1
  201. package/dist/lib/tui-runtime.js +1 -6
  202. package/dist/lib/tui-runtime.js.map +1 -1
  203. package/dist/lib/tui-sidebar-format.d.ts +1 -1
  204. package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
  205. package/dist/lib/tui-sidebar-format.js +1 -0
  206. package/dist/lib/tui-sidebar-format.js.map +1 -1
  207. package/dist/lib/types.d.ts +61 -7
  208. package/dist/lib/types.d.ts.map +1 -1
  209. package/dist/lib/types.js +2 -0
  210. package/dist/lib/types.js.map +1 -1
  211. package/dist/lib/xai.d.ts +39 -0
  212. package/dist/lib/xai.d.ts.map +1 -0
  213. package/dist/lib/xai.js +153 -0
  214. package/dist/lib/xai.js.map +1 -0
  215. package/dist/lib/zai.d.ts.map +1 -1
  216. package/dist/lib/zai.js +62 -56
  217. package/dist/lib/zai.js.map +1 -1
  218. package/dist/lib/zhipu.d.ts.map +1 -1
  219. package/dist/lib/zhipu.js +55 -50
  220. package/dist/lib/zhipu.js.map +1 -1
  221. package/dist/plugin.d.ts.map +1 -1
  222. package/dist/plugin.js +36 -157
  223. package/dist/plugin.js.map +1 -1
  224. package/dist/providers/copilot.d.ts.map +1 -1
  225. package/dist/providers/copilot.js +24 -3
  226. package/dist/providers/copilot.js.map +1 -1
  227. package/dist/providers/glm-coding-plan-provider.d.ts +35 -0
  228. package/dist/providers/glm-coding-plan-provider.d.ts.map +1 -0
  229. package/dist/providers/glm-coding-plan-provider.js +40 -0
  230. package/dist/providers/glm-coding-plan-provider.js.map +1 -0
  231. package/dist/providers/google-agy.d.ts.map +1 -1
  232. package/dist/providers/google-agy.js +60 -36
  233. package/dist/providers/google-agy.js.map +1 -1
  234. package/dist/providers/mimo.d.ts +3 -0
  235. package/dist/providers/mimo.d.ts.map +1 -0
  236. package/dist/providers/mimo.js +120 -0
  237. package/dist/providers/mimo.js.map +1 -0
  238. package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
  239. package/dist/providers/minimax-coding-plan.js +60 -37
  240. package/dist/providers/minimax-coding-plan.js.map +1 -1
  241. package/dist/providers/opencode-zen.d.ts +3 -0
  242. package/dist/providers/opencode-zen.d.ts.map +1 -0
  243. package/dist/providers/opencode-zen.js +94 -0
  244. package/dist/providers/opencode-zen.js.map +1 -0
  245. package/dist/providers/quota-providers.d.ts.map +1 -1
  246. package/dist/providers/quota-providers.js +5 -3
  247. package/dist/providers/quota-providers.js.map +1 -1
  248. package/dist/providers/registry.d.ts.map +1 -1
  249. package/dist/providers/registry.js +6 -0
  250. package/dist/providers/registry.js.map +1 -1
  251. package/dist/providers/xai.d.ts +6 -0
  252. package/dist/providers/xai.d.ts.map +1 -0
  253. package/dist/providers/xai.js +51 -0
  254. package/dist/providers/xai.js.map +1 -0
  255. package/dist/providers/zai.d.ts +1 -7
  256. package/dist/providers/zai.d.ts.map +1 -1
  257. package/dist/providers/zai.js +9 -45
  258. package/dist/providers/zai.js.map +1 -1
  259. package/dist/providers/zhipu.d.ts +1 -7
  260. package/dist/providers/zhipu.d.ts.map +1 -1
  261. package/dist/providers/zhipu.js +9 -45
  262. package/dist/providers/zhipu.js.map +1 -1
  263. package/dist/tui.d.ts.map +1 -1
  264. package/dist/tui.js +47 -136
  265. package/dist/tui.tsx +62 -179
  266. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { isIP } from "node:net";
2
2
  import { getQuotaProviderShape } from "./provider-metadata.js";
3
3
  import { lookupCost } from "./modelsdev-pricing.js";
4
4
  import { resolvePricingKey } from "./quota-stats.js";
5
- export const QUOTA_PROVIDER_REMOTE_FORMATS = ["accounting-v1", "openrouter-key-v1"];
5
+ export const QUOTA_PROVIDER_REMOTE_FORMATS = ["quota-v1", "openrouter-key-v1", "json-v1"];
6
6
  export const QUOTA_PROVIDER_MODES = ["remote-api", "local-estimate"];
7
7
  export const QUOTA_PROVIDER_WINDOW_TYPES = ["utc-day", "rolling"];
8
8
  export const QUOTA_PROVIDERS_AGGREGATE_ID = "quota-providers";
@@ -46,7 +46,7 @@ export function selectEligibleQuotaProviderDefinitions(params) {
46
46
  (definition.modelIds === undefined || definition.modelIds.includes(identity.modelId)));
47
47
  }
48
48
  const BASE_FIELDS = ["id", "providerId", "label", "mode", "modelIds"];
49
- const REMOTE_FIELDS = [...BASE_FIELDS, "url", "format", "apiKeyEnv"];
49
+ const REMOTE_FIELDS = [...BASE_FIELDS, "url", "format", "apiKeyEnv", "adapter"];
50
50
  const LOCAL_FIELDS = [...BASE_FIELDS, "windows", "pricingModelMap"];
51
51
  const WINDOW_FIELDS = [
52
52
  "id",
@@ -60,6 +60,7 @@ const ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
60
60
  const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
61
61
  const ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
62
62
  const ASCII_OR_UNICODE_CONTROL_PATTERN = /\p{Cc}/u;
63
+ const JSON_V1_STATIC_DISPLAY_FORBIDDEN_PATTERN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
63
64
  const URL_WHITESPACE_OR_CONTROL_PATTERN = /[\s\p{Cc}]/u;
64
65
  const MODEL_FORBIDDEN_PATTERN = /[\s\p{Cc}#?]/u;
65
66
  const MAX_ID_LENGTH = 64;
@@ -70,6 +71,35 @@ const MAX_WINDOWS = 16;
70
71
  const MAX_ROLLING_MINUTES = 366 * 24 * 60;
71
72
  const MAX_SAFE_REQUEST_LIMIT = 1_000_000_000;
72
73
  const MAX_USD_BUDGET = 1_000_000_000;
74
+ export const JSON_V1_MAX_MAPPINGS = 16;
75
+ export const JSON_V1_MAX_ADAPTER_DEPTH = 8;
76
+ export const JSON_V1_MAX_ADAPTER_OBJECTS = 128;
77
+ export const JSON_V1_MAX_ADAPTER_PROPERTIES = 384;
78
+ export const JSON_V1_MAX_ADAPTER_ARRAY_ELEMENTS = 640;
79
+ export const JSON_V1_MAX_PATH_SEGMENTS = 8;
80
+ export const JSON_V1_MAX_PATH_SEGMENT_CODE_POINTS = 64;
81
+ export const JSON_V1_MAX_STATIC_NAME_CODE_POINTS = 80;
82
+ export const JSON_V1_MAX_STATIC_UNIT_CODE_POINTS = 32;
83
+ export const JSON_V1_MAX_DISPLAY_CODE_POINTS = 160;
84
+ export const JSON_V1_MAX_NUMBER_MAGNITUDE = 1e15;
85
+ const JSON_V1_FORBIDDEN_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
86
+ const JSON_V1_DIVISORS = new Set([100, 1_000, 1_000_000]);
87
+ const JSON_V1_TIMESTAMP_ENCODINGS = new Set([
88
+ "iso-8601",
89
+ "unix-seconds",
90
+ "unix-milliseconds",
91
+ ]);
92
+ const JSON_V1_RESULT_TYPES = new Set([
93
+ "quota",
94
+ "rate_limit",
95
+ "usage",
96
+ "spend",
97
+ "budget",
98
+ "balance",
99
+ "status",
100
+ ]);
101
+ const JSON_V1_MIN_TIMESTAMP_MS = 0;
102
+ const JSON_V1_MAX_TIMESTAMP_MS = 253_402_300_799_999;
73
103
  function isPlainObject(value) {
74
104
  return typeof value === "object" && value !== null && !Array.isArray(value);
75
105
  }
@@ -151,10 +181,16 @@ function normalizeRemoteUrl(value) {
151
181
  }
152
182
  function cloneDefinition(definition) {
153
183
  if (definition.mode === "remote-api") {
154
- return {
155
- ...definition,
156
- ...(definition.modelIds ? { modelIds: [...definition.modelIds] } : {}),
157
- };
184
+ return definition.format === "json-v1"
185
+ ? {
186
+ ...definition,
187
+ ...(definition.modelIds ? { modelIds: [...definition.modelIds] } : {}),
188
+ adapter: structuredClone(definition.adapter),
189
+ }
190
+ : {
191
+ ...definition,
192
+ ...(definition.modelIds ? { modelIds: [...definition.modelIds] } : {}),
193
+ };
158
194
  }
159
195
  return {
160
196
  ...definition,
@@ -163,6 +199,487 @@ function cloneDefinition(definition) {
163
199
  ...(definition.pricingModelMap ? { pricingModelMap: { ...definition.pricingModelMap } } : {}),
164
200
  };
165
201
  }
202
+ function adapterIssue(issues, key, message) {
203
+ issues.push({ key, message });
204
+ }
205
+ function validateKnownAdapterFields(value, allowed, key, issues) {
206
+ if (Object.keys(value).some((field) => !allowed.includes(field))) {
207
+ adapterIssue(issues, `${key}.*`, `unknown field; allowed fields are ${allowed.join(", ")}`);
208
+ }
209
+ }
210
+ function preflightJsonV1Adapter(value, key, issues) {
211
+ const stack = [{ value, depth: 1 }];
212
+ const seen = new WeakSet();
213
+ let objects = 0;
214
+ let properties = 0;
215
+ let arrayElements = 0;
216
+ while (stack.length > 0) {
217
+ const current = stack.pop();
218
+ if (!current.value || typeof current.value !== "object")
219
+ continue;
220
+ if (seen.has(current.value)) {
221
+ adapterIssue(issues, key, "expected a bounded JSON object tree");
222
+ return false;
223
+ }
224
+ seen.add(current.value);
225
+ if (current.depth > JSON_V1_MAX_ADAPTER_DEPTH) {
226
+ adapterIssue(issues, key, `must not exceed nesting depth ${JSON_V1_MAX_ADAPTER_DEPTH}`);
227
+ return false;
228
+ }
229
+ if (Array.isArray(current.value)) {
230
+ arrayElements += current.value.length;
231
+ if (arrayElements > JSON_V1_MAX_ADAPTER_ARRAY_ELEMENTS) {
232
+ adapterIssue(issues, key, `must not exceed ${JSON_V1_MAX_ADAPTER_ARRAY_ELEMENTS} total array elements`);
233
+ return false;
234
+ }
235
+ for (let index = current.value.length - 1; index >= 0; index -= 1) {
236
+ stack.push({ value: current.value[index], depth: current.depth + 1 });
237
+ }
238
+ continue;
239
+ }
240
+ objects += 1;
241
+ const keys = Object.keys(current.value);
242
+ properties += keys.length;
243
+ if (objects > JSON_V1_MAX_ADAPTER_OBJECTS) {
244
+ adapterIssue(issues, key, `must not exceed ${JSON_V1_MAX_ADAPTER_OBJECTS} objects`);
245
+ return false;
246
+ }
247
+ if (properties > JSON_V1_MAX_ADAPTER_PROPERTIES) {
248
+ adapterIssue(issues, key, `must not exceed ${JSON_V1_MAX_ADAPTER_PROPERTIES} object properties`);
249
+ return false;
250
+ }
251
+ const record = current.value;
252
+ for (let index = keys.length - 1; index >= 0; index -= 1) {
253
+ stack.push({ value: record[keys[index]], depth: current.depth + 1 });
254
+ }
255
+ }
256
+ return true;
257
+ }
258
+ function normalizeJsonV1StaticText(value, maxCodePoints) {
259
+ if (typeof value !== "string" || JSON_V1_STATIC_DISPLAY_FORBIDDEN_PATTERN.test(value))
260
+ return null;
261
+ const normalized = value.trim();
262
+ const length = Array.from(normalized).length;
263
+ return length >= 1 && length <= maxCodePoints ? normalized : null;
264
+ }
265
+ function validateJsonV1Path(value, key, issues) {
266
+ if (!Array.isArray(value) || value.length < 1 || value.length > JSON_V1_MAX_PATH_SEGMENTS) {
267
+ adapterIssue(issues, key, `expected an array containing 1-${JSON_V1_MAX_PATH_SEGMENTS} path segments`);
268
+ return undefined;
269
+ }
270
+ const path = [];
271
+ for (let index = 0; index < value.length; index += 1) {
272
+ const segment = value[index];
273
+ if (typeof segment !== "string" ||
274
+ Array.from(segment).length < 1 ||
275
+ Array.from(segment).length > JSON_V1_MAX_PATH_SEGMENT_CODE_POINTS ||
276
+ ASCII_OR_UNICODE_CONTROL_PATTERN.test(segment) ||
277
+ JSON_V1_FORBIDDEN_PATH_SEGMENTS.has(segment)) {
278
+ adapterIssue(issues, `${key}[${index}]`, "expected a safe 1-64 code point property segment");
279
+ continue;
280
+ }
281
+ path.push(segment);
282
+ }
283
+ return path.length === value.length ? path : undefined;
284
+ }
285
+ const JSON_V1_ISO_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?(Z|([+-])(\d{2}):(\d{2}))$/;
286
+ function daysInMonth(year, month) {
287
+ if (month === 2) {
288
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
289
+ }
290
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
291
+ }
292
+ export function normalizeJsonV1Timestamp(value, encoding) {
293
+ let milliseconds;
294
+ if (encoding === "iso-8601") {
295
+ if (typeof value !== "string")
296
+ return null;
297
+ const match = JSON_V1_ISO_RE.exec(value);
298
+ if (!match)
299
+ return null;
300
+ const year = Number(match[1]);
301
+ const month = Number(match[2]);
302
+ const day = Number(match[3]);
303
+ const hour = Number(match[4]);
304
+ const minute = Number(match[5]);
305
+ const second = Number(match[6]);
306
+ const fraction = (match[7] ?? "").padEnd(3, "0");
307
+ const offsetHour = match[8] === "Z" ? 0 : Number(match[10]);
308
+ const offsetMinute = match[8] === "Z" ? 0 : Number(match[11]);
309
+ if (year < 1970 ||
310
+ month < 1 ||
311
+ month > 12 ||
312
+ day < 1 ||
313
+ day > daysInMonth(year, month) ||
314
+ hour > 23 ||
315
+ minute > 59 ||
316
+ second > 59 ||
317
+ offsetHour > 14 ||
318
+ offsetMinute > 59 ||
319
+ (offsetHour === 14 && offsetMinute !== 0)) {
320
+ return null;
321
+ }
322
+ const offset = match[8] === "Z"
323
+ ? 0
324
+ : (match[9] === "+" ? 1 : -1) * (offsetHour * 60 + offsetMinute) * 60_000;
325
+ milliseconds = Date.UTC(year, month - 1, day, hour, minute, second, Number(fraction)) - offset;
326
+ }
327
+ else {
328
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
329
+ return null;
330
+ }
331
+ milliseconds = encoding === "unix-seconds" ? value * 1_000 : value;
332
+ }
333
+ if (!Number.isSafeInteger(milliseconds) ||
334
+ milliseconds < JSON_V1_MIN_TIMESTAMP_MS ||
335
+ milliseconds > JSON_V1_MAX_TIMESTAMP_MS) {
336
+ return null;
337
+ }
338
+ return new Date(milliseconds).toISOString();
339
+ }
340
+ function validateJsonV1NumberSource(value, key, issues) {
341
+ if (!isPlainObject(value)) {
342
+ adapterIssue(issues, key, "expected a path or literal number source object");
343
+ return undefined;
344
+ }
345
+ const hasPath = hasOwnKey(value, "path");
346
+ const hasLiteral = hasOwnKey(value, "literal");
347
+ if (hasPath === hasLiteral) {
348
+ adapterIssue(issues, key, "expected exactly one of path or literal");
349
+ return undefined;
350
+ }
351
+ if (hasPath) {
352
+ validateKnownAdapterFields(value, ["path", "divideBy"], key, issues);
353
+ const path = validateJsonV1Path(value.path, `${key}.path`, issues);
354
+ if (hasOwnKey(value, "divideBy") &&
355
+ (typeof value.divideBy !== "number" || !JSON_V1_DIVISORS.has(value.divideBy))) {
356
+ adapterIssue(issues, `${key}.divideBy`, "expected 100, 1000, or 1000000");
357
+ }
358
+ if (path &&
359
+ (!hasOwnKey(value, "divideBy") ||
360
+ (typeof value.divideBy === "number" && JSON_V1_DIVISORS.has(value.divideBy)))) {
361
+ return {
362
+ path,
363
+ ...(value.divideBy !== undefined
364
+ ? { divideBy: value.divideBy }
365
+ : {}),
366
+ };
367
+ }
368
+ return undefined;
369
+ }
370
+ validateKnownAdapterFields(value, ["literal"], key, issues);
371
+ if (typeof value.literal !== "number" ||
372
+ !Number.isFinite(value.literal) ||
373
+ Math.abs(value.literal) > JSON_V1_MAX_NUMBER_MAGNITUDE) {
374
+ adapterIssue(issues, `${key}.literal`, `expected a finite number with absolute magnitude at most ${JSON_V1_MAX_NUMBER_MAGNITUDE}`);
375
+ return undefined;
376
+ }
377
+ return { literal: value.literal };
378
+ }
379
+ function validateJsonV1TextSource(value, key, issues) {
380
+ if (!isPlainObject(value)) {
381
+ adapterIssue(issues, key, "expected a path or literal text source object");
382
+ return undefined;
383
+ }
384
+ const hasPath = hasOwnKey(value, "path");
385
+ const hasLiteral = hasOwnKey(value, "literal");
386
+ if (hasPath === hasLiteral) {
387
+ adapterIssue(issues, key, "expected exactly one of path or literal");
388
+ return undefined;
389
+ }
390
+ validateKnownAdapterFields(value, hasPath ? ["path"] : ["literal"], key, issues);
391
+ if (hasPath) {
392
+ const path = validateJsonV1Path(value.path, `${key}.path`, issues);
393
+ return path ? { path } : undefined;
394
+ }
395
+ const literal = normalizeJsonV1StaticText(value.literal, JSON_V1_MAX_DISPLAY_CODE_POINTS);
396
+ if (!literal) {
397
+ adapterIssue(issues, `${key}.literal`, `expected 1-${JSON_V1_MAX_DISPLAY_CODE_POINTS} printable Unicode code points after trimming`);
398
+ return undefined;
399
+ }
400
+ return { literal };
401
+ }
402
+ function validateJsonV1TimestampSource(value, key, issues) {
403
+ if (!isPlainObject(value)) {
404
+ adapterIssue(issues, key, "expected a timestamp source object");
405
+ return undefined;
406
+ }
407
+ const hasPath = hasOwnKey(value, "path");
408
+ const hasLiteral = hasOwnKey(value, "literal");
409
+ if (hasPath === hasLiteral) {
410
+ adapterIssue(issues, key, "expected exactly one of path or literal");
411
+ return undefined;
412
+ }
413
+ validateKnownAdapterFields(value, hasPath ? ["path", "encoding"] : ["literal", "encoding"], key, issues);
414
+ const encoding = value.encoding;
415
+ if (typeof encoding !== "string" ||
416
+ !JSON_V1_TIMESTAMP_ENCODINGS.has(encoding)) {
417
+ adapterIssue(issues, `${key}.encoding`, 'expected "iso-8601", "unix-seconds", or "unix-milliseconds"');
418
+ return undefined;
419
+ }
420
+ const typedEncoding = encoding;
421
+ if (hasPath) {
422
+ const path = validateJsonV1Path(value.path, `${key}.path`, issues);
423
+ return path ? { path, encoding: typedEncoding } : undefined;
424
+ }
425
+ if (normalizeJsonV1Timestamp(value.literal, typedEncoding) === null) {
426
+ adapterIssue(issues, `${key}.literal`, "expected a valid timestamp for the selected encoding");
427
+ return undefined;
428
+ }
429
+ return { literal: value.literal, encoding: typedEncoding };
430
+ }
431
+ function literalNumber(source) {
432
+ return source && "literal" in source ? source.literal : undefined;
433
+ }
434
+ function requireLiteralNumber(source, key, issues, rule) {
435
+ const value = literalNumber(source);
436
+ if (value === undefined)
437
+ return;
438
+ if ((rule === "positive" && value <= 0) || (rule === "non-negative" && value < 0)) {
439
+ adapterIssue(issues, `${key}.literal`, rule === "positive"
440
+ ? "expected a number greater than zero"
441
+ : "expected a non-negative number");
442
+ }
443
+ }
444
+ function validateJsonV1Metric(value, resultType, key, issues) {
445
+ if (!isPlainObject(value)) {
446
+ adapterIssue(issues, key, "expected a tagged metric object");
447
+ return undefined;
448
+ }
449
+ if (typeof value.type !== "string") {
450
+ adapterIssue(issues, `${key}.type`, "field is required");
451
+ return undefined;
452
+ }
453
+ const issueStart = issues.length;
454
+ switch (value.type) {
455
+ case "percentage": {
456
+ validateKnownAdapterFields(value, ["type", "percentage", "meaning"], key, issues);
457
+ const percentage = validateJsonV1NumberSource(value.percentage, `${key}.percentage`, issues);
458
+ if (value.meaning !== "remaining" && value.meaning !== "used") {
459
+ adapterIssue(issues, `${key}.meaning`, 'expected "remaining" or "used"');
460
+ }
461
+ if (resultType && !["quota", "rate_limit", "budget"].includes(resultType)) {
462
+ adapterIssue(issues, `${key}.type`, "percentage is incompatible with resultType");
463
+ }
464
+ const literal = literalNumber(percentage);
465
+ if (literal !== undefined &&
466
+ ((value.meaning === "remaining" && literal > 100) ||
467
+ (value.meaning === "used" && literal < 0))) {
468
+ adapterIssue(issues, `${key}.percentage.literal`, value.meaning === "remaining"
469
+ ? "remaining percentage must not exceed 100"
470
+ : "used percentage must be non-negative");
471
+ }
472
+ return issueStart === issues.length && percentage
473
+ ? { type: "percentage", percentage, meaning: value.meaning }
474
+ : undefined;
475
+ }
476
+ case "used-limit": {
477
+ validateKnownAdapterFields(value, ["type", "used", "limit"], key, issues);
478
+ const used = validateJsonV1NumberSource(value.used, `${key}.used`, issues);
479
+ const limit = validateJsonV1NumberSource(value.limit, `${key}.limit`, issues);
480
+ requireLiteralNumber(used, `${key}.used`, issues, "non-negative");
481
+ requireLiteralNumber(limit, `${key}.limit`, issues, "positive");
482
+ if (resultType && !["quota", "rate_limit"].includes(resultType)) {
483
+ adapterIssue(issues, `${key}.type`, "used-limit is incompatible with resultType");
484
+ }
485
+ return issueStart === issues.length && used && limit
486
+ ? { type: "used-limit", used, limit }
487
+ : undefined;
488
+ }
489
+ case "remaining-limit": {
490
+ validateKnownAdapterFields(value, ["type", "remaining", "limit"], key, issues);
491
+ const remaining = validateJsonV1NumberSource(value.remaining, `${key}.remaining`, issues);
492
+ const limit = validateJsonV1NumberSource(value.limit, `${key}.limit`, issues);
493
+ requireLiteralNumber(limit, `${key}.limit`, issues, "positive");
494
+ const remainingLiteral = literalNumber(remaining);
495
+ const limitLiteral = literalNumber(limit);
496
+ if (remainingLiteral !== undefined &&
497
+ limitLiteral !== undefined &&
498
+ remainingLiteral > limitLiteral) {
499
+ adapterIssue(issues, `${key}.remaining.literal`, "remaining must not exceed limit");
500
+ }
501
+ if (resultType && !["quota", "rate_limit"].includes(resultType)) {
502
+ adapterIssue(issues, `${key}.type`, "remaining-limit is incompatible with resultType");
503
+ }
504
+ return issueStart === issues.length && remaining && limit
505
+ ? { type: "remaining-limit", remaining, limit }
506
+ : undefined;
507
+ }
508
+ case "spend-budget": {
509
+ validateKnownAdapterFields(value, ["type", "spend", "budget"], key, issues);
510
+ const spend = validateJsonV1NumberSource(value.spend, `${key}.spend`, issues);
511
+ const budget = validateJsonV1NumberSource(value.budget, `${key}.budget`, issues);
512
+ requireLiteralNumber(spend, `${key}.spend`, issues, "non-negative");
513
+ requireLiteralNumber(budget, `${key}.budget`, issues, "positive");
514
+ if (resultType && resultType !== "budget") {
515
+ adapterIssue(issues, `${key}.type`, "spend-budget is incompatible with resultType");
516
+ }
517
+ return issueStart === issues.length && spend && budget
518
+ ? { type: "spend-budget", spend, budget }
519
+ : undefined;
520
+ }
521
+ case "remaining-budget": {
522
+ validateKnownAdapterFields(value, ["type", "remaining", "budget"], key, issues);
523
+ const remaining = validateJsonV1NumberSource(value.remaining, `${key}.remaining`, issues);
524
+ const budget = validateJsonV1NumberSource(value.budget, `${key}.budget`, issues);
525
+ requireLiteralNumber(budget, `${key}.budget`, issues, "positive");
526
+ const remainingLiteral = literalNumber(remaining);
527
+ const budgetLiteral = literalNumber(budget);
528
+ if (remainingLiteral !== undefined &&
529
+ budgetLiteral !== undefined &&
530
+ remainingLiteral > budgetLiteral) {
531
+ adapterIssue(issues, `${key}.remaining.literal`, "remaining must not exceed budget");
532
+ }
533
+ if (resultType && resultType !== "budget") {
534
+ adapterIssue(issues, `${key}.type`, "remaining-budget is incompatible with resultType");
535
+ }
536
+ return issueStart === issues.length && remaining && budget
537
+ ? { type: "remaining-budget", remaining, budget }
538
+ : undefined;
539
+ }
540
+ case "value": {
541
+ validateKnownAdapterFields(value, ["type", "valueType", "value"], key, issues);
542
+ const source = validateJsonV1NumberSource(value.value, `${key}.value`, issues);
543
+ const allowed = {
544
+ used: ["quota", "rate_limit", "usage"],
545
+ limit: ["quota", "rate_limit"],
546
+ remaining: ["quota", "rate_limit"],
547
+ balance: ["balance"],
548
+ spend: ["spend"],
549
+ budget: ["budget"],
550
+ };
551
+ if (typeof value.valueType !== "string" || !hasOwnKey(allowed, value.valueType)) {
552
+ adapterIssue(issues, `${key}.valueType`, 'expected "used", "limit", "remaining", "balance", "spend", or "budget"');
553
+ }
554
+ else {
555
+ if (!["remaining", "balance"].includes(value.valueType)) {
556
+ requireLiteralNumber(source, `${key}.value`, issues, "non-negative");
557
+ }
558
+ if (resultType && !allowed[value.valueType].includes(resultType)) {
559
+ adapterIssue(issues, `${key}.valueType`, "valueType is incompatible with resultType");
560
+ }
561
+ }
562
+ return issueStart === issues.length && source
563
+ ? {
564
+ type: "value",
565
+ valueType: value.valueType,
566
+ value: source,
567
+ }
568
+ : undefined;
569
+ }
570
+ case "status": {
571
+ validateKnownAdapterFields(value, ["type", "value"], key, issues);
572
+ const source = validateJsonV1TextSource(value.value, `${key}.value`, issues);
573
+ if (resultType && resultType !== "status") {
574
+ adapterIssue(issues, `${key}.type`, "status is incompatible with resultType");
575
+ }
576
+ return issueStart === issues.length && source ? { type: "status", value: source } : undefined;
577
+ }
578
+ default:
579
+ adapterIssue(issues, `${key}.type`, "expected a supported tagged metric type");
580
+ return undefined;
581
+ }
582
+ }
583
+ function validateJsonV1Mapping(value, key, providerLabel, issues) {
584
+ if (!isPlainObject(value)) {
585
+ adapterIssue(issues, key, "expected an object");
586
+ return undefined;
587
+ }
588
+ const issueStart = issues.length;
589
+ validateKnownAdapterFields(value, ["resultType", "name", "label", "unit", "unitPosition", "resetTime", "observedTime", "metric"], key, issues);
590
+ for (const field of ["resultType", "name", "metric"]) {
591
+ if (!hasOwnKey(value, field))
592
+ adapterIssue(issues, `${key}.${field}`, "field is required");
593
+ }
594
+ const resultType = typeof value.resultType === "string" &&
595
+ JSON_V1_RESULT_TYPES.has(value.resultType)
596
+ ? value.resultType
597
+ : undefined;
598
+ if (hasOwnKey(value, "resultType") && !resultType) {
599
+ adapterIssue(issues, `${key}.resultType`, "expected a supported accounting result type");
600
+ }
601
+ const name = normalizeJsonV1StaticText(value.name, JSON_V1_MAX_STATIC_NAME_CODE_POINTS);
602
+ if (hasOwnKey(value, "name") && !name) {
603
+ adapterIssue(issues, `${key}.name`, `expected 1-${JSON_V1_MAX_STATIC_NAME_CODE_POINTS} printable Unicode code points after trimming`);
604
+ }
605
+ else if (name &&
606
+ Array.from(`${providerLabel} ${name}`).length > JSON_V1_MAX_DISPLAY_CODE_POINTS) {
607
+ adapterIssue(issues, `${key}.name`, `provider-prefixed name must not exceed ${JSON_V1_MAX_DISPLAY_CODE_POINTS} code points`);
608
+ }
609
+ const label = value.label === undefined
610
+ ? undefined
611
+ : normalizeJsonV1StaticText(value.label, JSON_V1_MAX_STATIC_NAME_CODE_POINTS);
612
+ if (value.label !== undefined && !label) {
613
+ adapterIssue(issues, `${key}.label`, `expected 1-${JSON_V1_MAX_STATIC_NAME_CODE_POINTS} printable Unicode code points after trimming`);
614
+ }
615
+ const unit = value.unit === undefined
616
+ ? undefined
617
+ : normalizeJsonV1StaticText(value.unit, JSON_V1_MAX_STATIC_UNIT_CODE_POINTS);
618
+ if (value.unit !== undefined && !unit) {
619
+ adapterIssue(issues, `${key}.unit`, `expected 1-${JSON_V1_MAX_STATIC_UNIT_CODE_POINTS} printable Unicode code points after trimming`);
620
+ }
621
+ const unitPosition = value.unitPosition === "prefix" || value.unitPosition === "suffix"
622
+ ? value.unitPosition
623
+ : undefined;
624
+ if (value.unitPosition !== undefined && !unitPosition) {
625
+ adapterIssue(issues, `${key}.unitPosition`, 'expected "prefix" or "suffix"');
626
+ }
627
+ if ((unit === undefined) !== (unitPosition === undefined)) {
628
+ adapterIssue(issues, `${key}.unitPosition`, "unit and unitPosition must be configured together");
629
+ }
630
+ const resetTime = value.resetTime === undefined
631
+ ? undefined
632
+ : validateJsonV1TimestampSource(value.resetTime, `${key}.resetTime`, issues);
633
+ const observedTime = value.observedTime === undefined
634
+ ? undefined
635
+ : validateJsonV1TimestampSource(value.observedTime, `${key}.observedTime`, issues);
636
+ const metric = validateJsonV1Metric(value.metric, resultType, `${key}.metric`, issues);
637
+ if (metric && (metric.type === "percentage" || metric.type === "status") && unit !== undefined) {
638
+ adapterIssue(issues, `${key}.unit`, "units are not allowed for percentage or status metrics");
639
+ }
640
+ if (issueStart !== issues.length || !resultType || !name || !metric)
641
+ return undefined;
642
+ return {
643
+ resultType,
644
+ name,
645
+ ...(label ? { label } : {}),
646
+ ...(unit ? { unit, unitPosition: unitPosition } : {}),
647
+ ...(resetTime ? { resetTime } : {}),
648
+ ...(observedTime ? { observedTime } : {}),
649
+ metric,
650
+ };
651
+ }
652
+ function validateJsonV1Adapter(value, key, providerLabel, issues) {
653
+ if (!isPlainObject(value)) {
654
+ adapterIssue(issues, key, "expected an object");
655
+ return undefined;
656
+ }
657
+ if (!preflightJsonV1Adapter(value, key, issues))
658
+ return undefined;
659
+ const issueStart = issues.length;
660
+ validateKnownAdapterFields(value, ["rowsPath", "mappings"], key, issues);
661
+ const rowsPath = value.rowsPath === undefined
662
+ ? undefined
663
+ : validateJsonV1Path(value.rowsPath, `${key}.rowsPath`, issues);
664
+ if (!hasOwnKey(value, "mappings")) {
665
+ adapterIssue(issues, `${key}.mappings`, "field is required");
666
+ }
667
+ if (!Array.isArray(value.mappings) ||
668
+ value.mappings.length < 1 ||
669
+ value.mappings.length > JSON_V1_MAX_MAPPINGS) {
670
+ adapterIssue(issues, `${key}.mappings`, `expected an array containing 1-${JSON_V1_MAX_MAPPINGS} mappings`);
671
+ return undefined;
672
+ }
673
+ const mappings = [];
674
+ for (let index = 0; index < value.mappings.length; index += 1) {
675
+ const mapping = validateJsonV1Mapping(value.mappings[index], `${key}.mappings[${index}]`, providerLabel, issues);
676
+ if (mapping)
677
+ mappings.push(mapping);
678
+ }
679
+ return issueStart === issues.length && mappings.length === value.mappings.length
680
+ ? { ...(rowsPath ? { rowsPath } : {}), mappings }
681
+ : undefined;
682
+ }
166
683
  function validateModelIds(params) {
167
684
  if (!hasOwnKey(params.raw, "modelIds"))
168
685
  return undefined;
@@ -515,13 +1032,29 @@ export function validateQuotaProviders(value) {
515
1032
  if (normalizedUrl.message) {
516
1033
  issues.push({ key: `${providerKey}.url`, message: normalizedUrl.message });
517
1034
  }
518
- const format = raw.format;
519
- if (hasOwnKey(raw, "format") &&
520
- (typeof format !== "string" ||
521
- !QUOTA_PROVIDER_REMOTE_FORMATS.includes(format))) {
1035
+ const rawFormat = raw.format;
1036
+ const format = rawFormat === "accounting-v1" ? "quota-v1" : rawFormat;
1037
+ const formatValid = typeof format === "string" &&
1038
+ QUOTA_PROVIDER_REMOTE_FORMATS.includes(format);
1039
+ if (hasOwnKey(raw, "format") && !formatValid) {
522
1040
  issues.push({
523
1041
  key: `${providerKey}.format`,
524
- message: 'expected "accounting-v1" or "openrouter-key-v1"',
1042
+ message: 'expected "quota-v1", "openrouter-key-v1", or "json-v1"',
1043
+ });
1044
+ }
1045
+ let adapter;
1046
+ if (format === "json-v1") {
1047
+ if (!hasOwnKey(raw, "adapter")) {
1048
+ issues.push({ key: `${providerKey}.adapter`, message: "field is required" });
1049
+ }
1050
+ else if (label) {
1051
+ adapter = validateJsonV1Adapter(raw.adapter, `${providerKey}.adapter`, label, issues);
1052
+ }
1053
+ }
1054
+ else if (hasOwnKey(raw, "adapter")) {
1055
+ issues.push({
1056
+ key: `${providerKey}.adapter`,
1057
+ message: "allowed only for json-v1 format",
525
1058
  });
526
1059
  }
527
1060
  const apiKeyEnv = raw.apiKeyEnv;
@@ -534,7 +1067,7 @@ export function validateQuotaProviders(value) {
534
1067
  message: "expected at most 128 characters matching ^[A-Z_][A-Z0-9_]*$",
535
1068
  });
536
1069
  }
537
- if (providerId && normalizedUrl.value && typeof format === "string") {
1070
+ if (providerId && normalizedUrl.value && formatValid) {
538
1071
  const identity = JSON.stringify([providerId, normalizedUrl.value, format, apiKeyEnv ?? ""]);
539
1072
  const first = requestIdentities.get(identity);
540
1073
  if (first !== undefined) {
@@ -552,20 +1085,24 @@ export function validateQuotaProviders(value) {
552
1085
  providerId &&
553
1086
  label &&
554
1087
  normalizedUrl.value &&
555
- typeof format === "string" &&
556
- QUOTA_PROVIDER_REMOTE_FORMATS.includes(format)) {
1088
+ formatValid) {
1089
+ const common = {
1090
+ id,
1091
+ providerId,
1092
+ label,
1093
+ mode: "remote-api",
1094
+ url: normalizedUrl.value,
1095
+ ...(apiKeyEnv !== undefined ? { apiKeyEnv: apiKeyEnv } : {}),
1096
+ ...(modelIds ? { modelIds } : {}),
1097
+ };
557
1098
  definitions.push({
558
1099
  index,
559
- config: {
560
- id,
561
- providerId,
562
- label,
563
- mode,
564
- url: normalizedUrl.value,
565
- format: format,
566
- ...(apiKeyEnv !== undefined ? { apiKeyEnv: apiKeyEnv } : {}),
567
- ...(modelIds ? { modelIds } : {}),
568
- },
1100
+ config: format === "json-v1"
1101
+ ? { ...common, format, adapter: adapter }
1102
+ : {
1103
+ ...common,
1104
+ format: format,
1105
+ },
569
1106
  });
570
1107
  }
571
1108
  }