@cortexkit/opencode-openai-auth 0.11.1 → 1.0.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 (204) hide show
  1. package/README.md +15 -26
  2. package/dist/index-766sfrh5.js +1 -0
  3. package/dist/index-8kj7sa1p.js +12 -0
  4. package/dist/index-b3q3j39x.js +2 -0
  5. package/dist/index-bmd7x5aq.js +2 -0
  6. package/dist/index-eqh53k8v.js +2 -0
  7. package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
  8. package/dist/index-jyr8y8k4.js +7 -0
  9. package/dist/index-rv08jmd4.js +2 -0
  10. package/dist/index-ww91hyzr.js +221 -0
  11. package/dist/index.d.ts +578 -145
  12. package/dist/index.js +1 -110
  13. package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
  14. package/dist/port-file-Do3lQIEk.d.ts +29 -0
  15. package/dist/protocol-BOBdkqqm.d.ts +198 -0
  16. package/dist/rpc/port-file.d.ts +4 -17
  17. package/dist/rpc/port-file.js +1 -1
  18. package/dist/rpc/protocol.d.ts +2 -2
  19. package/dist/rpc/protocol.js +1 -0
  20. package/dist/rpc/rpc-client.d.ts +5 -4
  21. package/dist/rpc/rpc-client.js +1 -1
  22. package/dist/rpc/rpc-dir.d.ts +2 -0
  23. package/dist/rpc/rpc-dir.js +1 -1
  24. package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
  25. package/dist/sidebar-state.d.ts +2 -2
  26. package/dist/sidebar-state.js +1 -1
  27. package/dist/token-util-wdza526f.js +1 -0
  28. package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
  29. package/dist/tui-preferences.js +5 -2
  30. package/dist/tui.d.ts +9 -4
  31. package/dist/v2/server.d.ts +54 -0
  32. package/dist/v2/server.js +4 -0
  33. package/package.json +21 -9
  34. package/src/tui/entry.mjs +26 -25
  35. package/src/tui-compiled/raw/logger.js +24 -0
  36. package/src/tui-compiled/raw/rpc/protocol.js +3 -0
  37. package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
  38. package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
  39. package/src/tui-compiled/raw/selector.js +21 -0
  40. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
  41. package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
  42. package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
  43. package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
  44. package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
  45. package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
  46. package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
  47. package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
  48. package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
  49. package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
  50. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  51. package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
  52. package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
  53. package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
  54. package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
  55. package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
  56. package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
  57. package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
  58. package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
  59. package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
  60. package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
  61. package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
  62. package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
  63. package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
  64. package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  65. package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
  66. package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
  67. package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
  68. package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
  69. package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
  70. package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
  71. package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
  72. package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  73. package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
  74. package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
  75. package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  76. package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
  77. package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
  78. package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
  79. package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
  80. package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  81. package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
  82. package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
  83. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
  84. package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
  85. package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
  86. package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
  87. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
  88. package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
  89. package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
  90. package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
  91. package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
  92. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  93. package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
  94. package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
  95. package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  96. package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  97. package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
  98. package/src/tui-compiled/raw/sidebar-state.js +960 -0
  99. package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
  100. package/src/tui-compiled/raw/tui-preferences.js +135 -0
  101. package/src/tui-compiled/raw/tui.tsx +135 -143
  102. package/src/tui-compiled/runtime/logger.js +24 -0
  103. package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
  104. package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
  105. package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
  106. package/src/tui-compiled/runtime/selector.js +21 -0
  107. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
  108. package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
  109. package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
  110. package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
  111. package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
  112. package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
  113. package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
  114. package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
  115. package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
  116. package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
  117. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
  118. package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
  119. package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
  120. package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
  121. package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
  122. package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
  123. package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
  124. package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
  125. package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
  126. package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
  127. package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
  128. package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
  129. package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
  130. package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
  131. package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
  132. package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
  133. package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
  134. package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
  135. package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
  136. package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
  137. package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
  138. package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
  139. package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
  140. package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
  141. package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
  142. package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
  143. package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
  144. package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
  145. package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
  146. package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
  147. package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
  148. package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
  149. package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
  150. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
  151. package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
  152. package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
  153. package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
  154. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
  155. package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
  156. package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
  157. package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
  158. package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
  159. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
  160. package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
  161. package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
  162. package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
  163. package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
  164. package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
  165. package/src/tui-compiled/runtime/sidebar-state.js +960 -0
  166. package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
  167. package/src/tui-compiled/runtime/tui-preferences.js +135 -0
  168. package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
  169. package/dist/index-4effjmr6.js +0 -2
  170. package/dist/index-9ehhb051.js +0 -2
  171. package/dist/index-g3hsdjh4.js +0 -2
  172. package/dist/index-hh52v8sb.js +0 -2
  173. package/dist/index-jwn4r3sw.js +0 -1
  174. package/dist/index-sq36b120.js +0 -211
  175. package/dist/internal-BTndPmW2.d.ts +0 -914
  176. package/dist/quota-normalize-08hpp51v.js +0 -1
  177. package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
  178. package/dist/token-util-ypgz7rge.js +0 -1
  179. package/src/tui-compiled/raw/core-account-paths.ts +0 -114
  180. package/src/tui-compiled/raw/core-custody-state.ts +0 -243
  181. package/src/tui-compiled/raw/logger.ts +0 -53
  182. package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
  183. package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
  184. package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
  185. package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
  186. package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
  187. package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
  188. package/src/tui-compiled/raw/tui-preferences.ts +0 -328
  189. package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
  190. package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
  191. package/src/tui-compiled/runtime/logger.ts +0 -53
  192. package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
  193. package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
  194. package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
  195. package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
  196. package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
  197. package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
  198. package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
  199. package/src/tui-compiled/shared/internal.ts +0 -6
  200. package/src/tui-compiled/shared/logger.ts +0 -264
  201. package/src/tui-compiled/shared/paths.ts +0 -33
  202. package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
  203. package/src/tui-compiled/shared/util/error.ts +0 -9
  204. package/src/tui-compiled/shared/util/open-url.ts +0 -30
@@ -0,0 +1,300 @@
1
+ // One slash command per plugin: the menu a plugin creates once and serves to
2
+ // every invocation. It keeps no per-invocation state; each `open` and `apply`
3
+ // builds its sections afresh from the store and gets its own copy of the
4
+ // caller's context.
5
+ import { createLogger } from "./cc9e0702a752cea7-index.js";
6
+ import { builtinSections, } from "./754d6017f8e419cf-builtins.js";
7
+ import { applyResult, confirmationOf, createTextRedactor, dialogPayload, projectFailure, } from "./94820e324f2b4c09-seam.js";
8
+ const BUILTIN_IDS = new Set([
9
+ 'accounts',
10
+ 'quota',
11
+ 'routing',
12
+ 'limits',
13
+ 'cache',
14
+ 'diagnostics',
15
+ ]);
16
+ const STORE_SLOTS = [
17
+ 'accounts',
18
+ 'quota',
19
+ 'routing',
20
+ 'limits',
21
+ ];
22
+ /**
23
+ * A private copy of the caller's context, taken before the first await. Work
24
+ * an invocation leaves running reports through this copy, so a host that
25
+ * reuses and rebinds one context object for the next session cannot pull an
26
+ * earlier invocation's feedback over to it.
27
+ */
28
+ function ownInvocation(invocation, redact) {
29
+ const { sessionId } = invocation;
30
+ const notify = invocation.notify.bind(invocation);
31
+ // A notification leaves the process just as a payload does, so its text
32
+ // goes through the same redactor.
33
+ return Object.freeze({
34
+ ...(sessionId !== undefined ? { sessionId } : {}),
35
+ notify: (message, kind) => kind === undefined
36
+ ? notify(redact(String(message)))
37
+ : notify(redact(String(message)), kind),
38
+ });
39
+ }
40
+ function coerceOne(knob, raw) {
41
+ switch (knob.kind) {
42
+ case 'choice': {
43
+ const value = raw ?? knob.value;
44
+ if (typeof value === 'string' &&
45
+ knob.choices.some((choice) => choice.value === value))
46
+ return { value };
47
+ return { problem: `Choose one of the options for ${knob.label}.` };
48
+ }
49
+ case 'toggle': {
50
+ const value = raw ?? knob.value;
51
+ return typeof value === 'boolean'
52
+ ? { value }
53
+ : { problem: `${knob.label} must be on or off.` };
54
+ }
55
+ case 'number': {
56
+ const value = raw === undefined ? (knob.value ?? null) : raw;
57
+ if (value === null || value === '') {
58
+ return knob.required
59
+ ? { problem: `${knob.label} needs a number.` }
60
+ : { value: null };
61
+ }
62
+ const number = typeof value === 'number' ? value : Number(value);
63
+ if (typeof value === 'boolean' || !Number.isFinite(number))
64
+ return { problem: `${knob.label} needs a number.` };
65
+ if (knob.min !== undefined && number < knob.min)
66
+ return { problem: `${knob.label} must be at least ${knob.min}.` };
67
+ if (knob.max !== undefined && number > knob.max)
68
+ return { problem: `${knob.label} must be at most ${knob.max}.` };
69
+ return { value: number };
70
+ }
71
+ case 'text': {
72
+ const value = raw === undefined ? (knob.value ?? null) : raw;
73
+ if (value === null || value === '')
74
+ return knob.required
75
+ ? { problem: `${knob.label} cannot be empty.` }
76
+ : { value: null };
77
+ return typeof value === 'string'
78
+ ? { value }
79
+ : { problem: `${knob.label} must be text.` };
80
+ }
81
+ }
82
+ }
83
+ /** The action's inputs, checked against its knobs; unknown names are ignored. */
84
+ function coerceValues(knobs, raw) {
85
+ const values = {};
86
+ for (const knob of knobs) {
87
+ const given = raw && Object.hasOwn(raw, knob.id) ? raw[knob.id] : undefined;
88
+ const result = coerceOne(knob, given);
89
+ if ('problem' in result)
90
+ return result;
91
+ values[knob.id] = result.value;
92
+ }
93
+ return { values };
94
+ }
95
+ function findAction(sections, request) {
96
+ const section = sections.find((entry) => entry.id === request.sectionId);
97
+ if (!section)
98
+ return undefined;
99
+ const actions = request.itemId === undefined
100
+ ? section.content.actions
101
+ : section.content.items?.find((item) => item.id === request.itemId)
102
+ ?.actions;
103
+ return actions?.find((action) => action.id === request.actionId);
104
+ }
105
+ export function createCommandMenu(options) {
106
+ const logger = options.logger ?? createLogger('commands');
107
+ const redact = createTextRedactor(options.redaction);
108
+ const seam = { logger, redact };
109
+ const now = options.now ?? Date.now;
110
+ const extras = options.extras ?? [];
111
+ const replaced = options.replace ?? {};
112
+ const builtSlots = STORE_SLOTS.filter((slot) => !replaced[slot]);
113
+ for (const slot of STORE_SLOTS) {
114
+ if (replaced[slot] && options[slot] !== undefined)
115
+ throw new Error(`the ${slot} section is replaced; its built-in options cannot also be given`);
116
+ }
117
+ const store = options.store;
118
+ if (builtSlots.length > 0 && !store)
119
+ throw new Error(`a store is required for the built-in ${builtSlots.join(', ')} section${builtSlots.length > 1 ? 's' : ''}`);
120
+ const seen = new Set();
121
+ for (const extra of extras) {
122
+ if (BUILTIN_IDS.has(extra.id) || seen.has(extra.id))
123
+ throw new Error(`extra section id ${extra.id} is taken`);
124
+ seen.add(extra.id);
125
+ }
126
+ async function plugin(id, slot, section, invocation) {
127
+ try {
128
+ return {
129
+ id,
130
+ slot,
131
+ title: section.title,
132
+ content: await section.build(invocation),
133
+ };
134
+ }
135
+ catch (error) {
136
+ // One broken plugin section must not take the whole menu down.
137
+ logger.warn('command menu section failed to build', {
138
+ command: options.command,
139
+ section: id,
140
+ error: redact(error instanceof Error ? error.message : String(error)),
141
+ });
142
+ return {
143
+ id,
144
+ slot,
145
+ title: section.title,
146
+ content: { lines: ['This section could not be loaded.'] },
147
+ };
148
+ }
149
+ }
150
+ /** Every section, in the fixed slot order. */
151
+ async function sections(invocation) {
152
+ // Built-in sections read the store once; a fully replaced menu never
153
+ // touches it.
154
+ const built = store && builtSlots.length > 0
155
+ ? await builtinSections({
156
+ store,
157
+ now,
158
+ ...(options.extraLocks ? { extraLocks: options.extraLocks } : {}),
159
+ ...(options.accounts ? { accounts: options.accounts } : {}),
160
+ ...(options.quota ? { quota: options.quota } : {}),
161
+ ...(options.routing ? { routing: options.routing } : {}),
162
+ ...(options.limits ? { limits: options.limits } : {}),
163
+ })
164
+ : [];
165
+ const out = [];
166
+ for (const slot of STORE_SLOTS) {
167
+ const replacement = replaced[slot];
168
+ if (replacement)
169
+ out.push(await plugin(slot, slot, replacement, invocation));
170
+ else {
171
+ const section = built.find((entry) => entry.slot === slot);
172
+ if (section)
173
+ out.push(section);
174
+ }
175
+ }
176
+ if (options.cache)
177
+ out.push(await plugin('cache', 'cache', options.cache, invocation));
178
+ if (options.diagnostics)
179
+ out.push(await plugin('diagnostics', 'diagnostics', options.diagnostics, invocation));
180
+ for (const extra of extras)
181
+ out.push(await plugin(extra.id, 'extra', extra, invocation));
182
+ return out;
183
+ }
184
+ return {
185
+ command: options.command,
186
+ async open(invocation) {
187
+ const own = ownInvocation(invocation, redact);
188
+ return dialogPayload(options.command, options.title, await sections(own), seam);
189
+ },
190
+ async apply(request, invocation) {
191
+ const own = ownInvocation(invocation, redact);
192
+ const finish = async (outcome) => applyResult(options.command, options.title, await sections(own), outcome, seam);
193
+ const action = request.command === options.command
194
+ ? findAction(await sections(own), request)
195
+ : undefined;
196
+ if (!action)
197
+ return finish({
198
+ ok: false,
199
+ code: 'unavailable',
200
+ text: 'That action is no longer available.',
201
+ });
202
+ const confirmation = confirmationOf(action);
203
+ if (confirmation && request.confirmed !== true)
204
+ return finish({
205
+ ok: false,
206
+ code: 'needs-confirmation',
207
+ text: confirmation.message,
208
+ needsConfirmation: true,
209
+ });
210
+ const coerced = coerceValues(action.knobs ?? [], request.values);
211
+ if ('problem' in coerced)
212
+ return finish({
213
+ ok: false,
214
+ code: 'invalid-input',
215
+ text: coerced.problem,
216
+ });
217
+ let outcome;
218
+ try {
219
+ const result = await action.run({
220
+ values: coerced.values,
221
+ ...(request.itemId !== undefined ? { itemId: request.itemId } : {}),
222
+ invocation: own,
223
+ });
224
+ outcome =
225
+ typeof result === 'string'
226
+ ? { ok: true, text: result }
227
+ : {
228
+ ok: result.ok,
229
+ text: result.text,
230
+ ...(result.ok ? {} : { code: result.code ?? 'refused' }),
231
+ };
232
+ }
233
+ catch (error) {
234
+ // The raw text goes only to the log (redacted); the user sees the
235
+ // projected code and message, never the exception's own words.
236
+ const failure = projectFailure(error);
237
+ logger.warn('command menu action failed', {
238
+ command: options.command,
239
+ section: request.sectionId,
240
+ action: request.actionId,
241
+ code: failure.code,
242
+ error: redact(error instanceof Error ? error.message : String(error)),
243
+ });
244
+ outcome = { ok: false, ...failure };
245
+ }
246
+ return finish(outcome);
247
+ },
248
+ };
249
+ }
250
+ function isKnobValue(value) {
251
+ return (value === null ||
252
+ typeof value === 'string' ||
253
+ typeof value === 'boolean' ||
254
+ (typeof value === 'number' && Number.isFinite(value)));
255
+ }
256
+ /**
257
+ * Checks an apply request that arrived over the loopback RPC; undefined when
258
+ * it is not one. Only the request's own fields are kept.
259
+ */
260
+ export function parseApplyRequest(value) {
261
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
262
+ return undefined;
263
+ const raw = value;
264
+ if (typeof raw.command !== 'string' ||
265
+ typeof raw.sectionId !== 'string' ||
266
+ typeof raw.actionId !== 'string')
267
+ return undefined;
268
+ if (raw.itemId !== undefined && typeof raw.itemId !== 'string')
269
+ return undefined;
270
+ if (raw.sessionId !== undefined && typeof raw.sessionId !== 'string')
271
+ return undefined;
272
+ if (raw.confirmed !== undefined && typeof raw.confirmed !== 'boolean')
273
+ return undefined;
274
+ const values = {};
275
+ if (raw.values !== undefined) {
276
+ if (raw.values === null ||
277
+ typeof raw.values !== 'object' ||
278
+ Array.isArray(raw.values))
279
+ return undefined;
280
+ for (const [name, entry] of Object.entries(raw.values)) {
281
+ if (!isKnobValue(entry))
282
+ return undefined;
283
+ Object.defineProperty(values, name, {
284
+ value: entry,
285
+ enumerable: true,
286
+ writable: true,
287
+ configurable: true,
288
+ });
289
+ }
290
+ }
291
+ return {
292
+ command: raw.command,
293
+ sectionId: raw.sectionId,
294
+ actionId: raw.actionId,
295
+ ...(typeof raw.itemId === 'string' ? { itemId: raw.itemId } : {}),
296
+ ...(raw.values !== undefined ? { values } : {}),
297
+ ...(raw.confirmed === true ? { confirmed: true } : {}),
298
+ ...(typeof raw.sessionId === 'string' ? { sessionId: raw.sessionId } : {}),
299
+ };
300
+ }
@@ -0,0 +1,339 @@
1
+ // `sticky-balanced` routing: openai-auth's quota-weighted session placement
2
+ // and pin-break classification, judged over the quota projection rather than
3
+ // openai-auth's fixed primary/secondary snapshot.
4
+ import { budgetExhaustedResetAt, projectQuota, } from "./39c3cb05738fcf6d-projection.js";
5
+ import { admit, } from "./45bf5343bbbf9add-admission.js";
6
+ import { isPinValid } from "./c6b176708c446d6d-pins.js";
7
+ export const QUOTA_STALENESS_MS = 15 * 60_000;
8
+ export const MIN_RESET_HOURS = 1 / 60;
9
+ export const MIN_WEIGHT = 1e-6;
10
+ /** The projection's time, else the caller's cache-entry time. */
11
+ export function snapshotCheckedAt(quota, entryCheckedAt) {
12
+ for (const checkedAt of [quota?.checkedAt, entryCheckedAt]) {
13
+ if (typeof checkedAt === 'number' && Number.isFinite(checkedAt)) {
14
+ return checkedAt;
15
+ }
16
+ }
17
+ return undefined;
18
+ }
19
+ /**
20
+ * The status rule used when the adapter supplies none, carried from
21
+ * openai-auth: 401 and 403 are permanent; no response, 0, a non-finite
22
+ * status, 429 and 5xx are transient; anything else leaves the row healthy.
23
+ * A provider whose 403 can mean an organisation or model policy rather than
24
+ * a dead account supplies its own classifier and can fall back to this one
25
+ * for the statuses it does not single out.
26
+ */
27
+ export function defaultStickyStatusClass(status) {
28
+ if (status === 401 || status === 403)
29
+ return 'permanent';
30
+ if (status === undefined ||
31
+ status === 0 ||
32
+ !Number.isFinite(status) ||
33
+ (status >= 500 && status <= 599) ||
34
+ status === 429) {
35
+ return 'transient';
36
+ }
37
+ return 'healthy';
38
+ }
39
+ function windowReadings(quota) {
40
+ // Every reading is an independent constraint, so all of them are judged:
41
+ // a third window (for example a short window beside two weekly ones) can
42
+ // be the one that is nearly spent. Longest known window first, unknown
43
+ // lengths last, as openai-auth sorts, so a break decision with several
44
+ // spent windows names the longest. Tombstones and absence records carry no
45
+ // capacity figure and are left out.
46
+ return quota.limits
47
+ .filter((limit) => limit.kind === 'reading')
48
+ .sort((left, right) => {
49
+ const leftKnown = left.windowMinutes !== undefined;
50
+ const rightKnown = right.windowMinutes !== undefined;
51
+ if (leftKnown !== rightKnown)
52
+ return leftKnown ? -1 : 1;
53
+ if (leftKnown && rightKnown) {
54
+ return (right.windowMinutes ?? 0) - (left.windowMinutes ?? 0);
55
+ }
56
+ return 0;
57
+ });
58
+ }
59
+ /** Classifies whether a pinned session should leave its row after a failure. */
60
+ export function decideStickyBreak(input) {
61
+ const statusClass = (input.classifyStatus ?? defaultStickyStatusClass)(input.status);
62
+ if (statusClass === 'permanent') {
63
+ return { action: 'migrate', reason: 'permanent' };
64
+ }
65
+ if (!input.quota)
66
+ return { action: 'retain', reason: 'unknown' };
67
+ const checkedAt = snapshotCheckedAt(input.quota, input.quotaCheckedAt);
68
+ if (checkedAt === undefined ||
69
+ !Number.isFinite(checkedAt) ||
70
+ input.now - checkedAt > QUOTA_STALENESS_MS) {
71
+ return { action: 'retain', reason: 'stale' };
72
+ }
73
+ // After the stale check, so a stale snapshot never judges the account on a
74
+ // reading the killswitch would consider below its floor.
75
+ if (input.killswitchPasses === false) {
76
+ return { action: 'migrate', reason: 'killswitch' };
77
+ }
78
+ for (const limit of windowReadings(input.quota)) {
79
+ const remaining = limit.remainingPercent;
80
+ if (typeof remaining === 'number' &&
81
+ Number.isFinite(remaining) &&
82
+ remaining <= 0) {
83
+ return {
84
+ action: 'migrate',
85
+ reason: 'exhausted',
86
+ window: { scope: limit.scope, label: limit.label },
87
+ ...(typeof limit.resetsAt === 'string'
88
+ ? { resetsAt: limit.resetsAt }
89
+ : {}),
90
+ };
91
+ }
92
+ }
93
+ // A reached credit budget is exhaustion on its own axis, judged by the same
94
+ // signal admission uses so the two never disagree on what "spent" means.
95
+ const budgetReset = budgetExhaustedResetAt(input.quota, input.now);
96
+ if (budgetReset) {
97
+ return {
98
+ action: 'migrate',
99
+ reason: 'exhausted',
100
+ resetsAt: budgetReset.resetsAt,
101
+ };
102
+ }
103
+ return { action: 'retain', reason: statusClass };
104
+ }
105
+ export function sustainableWindowWeight(window, reservePercent, now) {
106
+ const spendable = Math.max(0, window.remainingPercent - reservePercent);
107
+ if (spendable <= 0)
108
+ return 0;
109
+ if (!window.resetsAt)
110
+ return spendable;
111
+ const resetMs = Date.parse(window.resetsAt);
112
+ // A lapsed reset cannot yield a spend rate: the divisor would clamp to
113
+ // MIN_RESET_HOURS and inflate the weight about sixty-fold on stale
114
+ // information, so the un-rate-adjusted spendable capacity is used instead.
115
+ if (!Number.isFinite(resetMs) || resetMs <= now)
116
+ return spendable;
117
+ const hours = Math.max((resetMs - now) / 3_600_000, MIN_RESET_HOURS);
118
+ return spendable / hours;
119
+ }
120
+ function compareAccountIds(left, right) {
121
+ if (left < right)
122
+ return -1;
123
+ if (left > right)
124
+ return 1;
125
+ return 0;
126
+ }
127
+ function candidateWeight(candidate, now) {
128
+ if (!candidate.quota)
129
+ return undefined;
130
+ const quotaCheckedAt = snapshotCheckedAt(candidate.quota, candidate.quotaCheckedAt);
131
+ if (quotaCheckedAt === undefined ||
132
+ now - quotaCheckedAt > QUOTA_STALENESS_MS) {
133
+ return undefined;
134
+ }
135
+ // Missing reserve data must leave a window usable rather than silently
136
+ // excluding its account.
137
+ // The account is as constrained as its tightest window, whichever it is.
138
+ const weights = windowReadings(candidate.quota).map((limit) => sustainableWindowWeight({
139
+ remainingPercent: limit.remainingPercent ?? Number.NaN,
140
+ ...(limit.resetsAt === undefined ? {} : { resetsAt: limit.resetsAt }),
141
+ }, candidate.reservePercent[limit.label] ?? 0, now));
142
+ // The credit budget is a third pressure axis on its own reset clock. It has
143
+ // no configured reserve, and a malformed reading is ignored rather than
144
+ // allowed to zero the account's weight.
145
+ const budget = candidate.quota.budget;
146
+ if (budget &&
147
+ typeof budget.remainingPercent === 'number' &&
148
+ Number.isFinite(budget.remainingPercent)) {
149
+ weights.push(sustainableWindowWeight({
150
+ remainingPercent: budget.remainingPercent,
151
+ ...(budget.resetsAt === undefined
152
+ ? {}
153
+ : { resetsAt: budget.resetsAt }),
154
+ }, 0, now));
155
+ }
156
+ const weight = weights.length > 0 ? Math.min(...weights) : 0;
157
+ return weight > 0 ? { candidate, quotaCheckedAt, weight } : undefined;
158
+ }
159
+ /**
160
+ * Places a session: the lowest projected pressure among candidates with a
161
+ * fresh positive weight, else (`mode-fallback`) the first candidate in
162
+ * configured order, preferring one with an applicable reset credit.
163
+ */
164
+ export function selectStickyCandidate(input) {
165
+ // A candidate killed by the killswitch is excluded from BOTH weighted
166
+ // placement and the fallback branch, which must never become a way to
167
+ // spend on a killed account.
168
+ const eligibleCandidates = input.candidates.filter((candidate) => candidate.killswitchPasses !== false);
169
+ if (input.candidates.length === 0) {
170
+ throw new Error('Cannot select a sticky candidate: input.candidates is empty');
171
+ }
172
+ if (eligibleCandidates.length === 0)
173
+ return undefined;
174
+ const weighted = eligibleCandidates
175
+ .map((candidate) => candidateWeight(candidate, input.now))
176
+ .filter((candidate) => candidate !== undefined);
177
+ if (weighted.length > 0) {
178
+ weighted.sort((left, right) => {
179
+ // MIN_WEIGHT only guards the division; every weight here is positive.
180
+ const leftScore = ((input.pendingBytes.get(left.candidate.accountId) ?? 0) +
181
+ input.requestBytes) /
182
+ Math.max(left.weight, MIN_WEIGHT);
183
+ const rightScore = ((input.pendingBytes.get(right.candidate.accountId) ?? 0) +
184
+ input.requestBytes) /
185
+ Math.max(right.weight, MIN_WEIGHT);
186
+ return (leftScore - rightScore ||
187
+ left.candidate.configuredOrder - right.candidate.configuredOrder ||
188
+ compareAccountIds(left.candidate.accountId, right.candidate.accountId));
189
+ });
190
+ const selected = weighted[0];
191
+ if (selected) {
192
+ return {
193
+ accountId: selected.candidate.accountId,
194
+ quotaCheckedAt: selected.quotaCheckedAt,
195
+ source: 'weighted',
196
+ };
197
+ }
198
+ }
199
+ input.onEmptyWeightedSet?.();
200
+ const fallback = [...eligibleCandidates].sort((left, right) => {
201
+ const leftHasCredits = (left.resetCreditsApplicable ?? 0) > 0 ? 1 : 0;
202
+ const rightHasCredits = (right.resetCreditsApplicable ?? 0) > 0 ? 1 : 0;
203
+ return (rightHasCredits - leftHasCredits ||
204
+ left.configuredOrder - right.configuredOrder ||
205
+ compareAccountIds(left.accountId, right.accountId));
206
+ })[0];
207
+ if (!fallback) {
208
+ throw new Error('Cannot select a sticky candidate: input.candidates is empty');
209
+ }
210
+ return {
211
+ accountId: fallback.accountId,
212
+ quotaCheckedAt: snapshotCheckedAt(fallback.quota, fallback.quotaCheckedAt),
213
+ source: 'mode-fallback',
214
+ };
215
+ }
216
+ /**
217
+ * Routes one request in `sticky-balanced` mode. A valid pin whose row is
218
+ * admitted, not excluded and not killed is dispatched as is. Otherwise
219
+ * selection runs over the non-excluded rows; each selected row admission
220
+ * refused is removed and selection re-runs, so the loop ends either on an
221
+ * admitted row or with no admissible account.
222
+ */
223
+ export function routeSticky(input) {
224
+ const admission = admit(input);
225
+ const admitted = new Map(admission.admitted.map((row) => [row.id, row]));
226
+ const refusals = new Map(admission.refused.map((r) => [r.id, r]));
227
+ const excluded = new Set(admission.excluded.map((row) => row.id));
228
+ const validIds = new Set(input.rows.map((row) => row.id));
229
+ const pinValid = input.pin !== undefined &&
230
+ isPinValid(input.pin, validIds, input.identities?.get(input.pin.accountId));
231
+ if (pinValid && input.pin) {
232
+ const pinned = admitted.get(input.pin.accountId);
233
+ if (pinned && input.killswitch?.get(pinned.id) !== false) {
234
+ return {
235
+ outcome: 'dispatch',
236
+ accountId: pinned.id,
237
+ source: 'pin',
238
+ ...(pinned.projection?.checkedAt === undefined
239
+ ? {}
240
+ : { quotaCheckedAt: pinned.projection.checkedAt }),
241
+ pin: { action: 'retain' },
242
+ refusedSelections: [],
243
+ admission,
244
+ };
245
+ }
246
+ }
247
+ // A spent window with a future reset, a spent credit budget and a killswitch
248
+ // verdict all say the pinned row will not serve until some known later time,
249
+ // so the pin may move. A row refused for want of a usable reading, or
250
+ // excluded by a short rate-limit mark or refresh backoff, may serve again on
251
+ // the next reading, so its pin stays.
252
+ const pinRefusal = input.pin ? refusals.get(input.pin.accountId) : undefined;
253
+ const pinMoves = pinValid &&
254
+ input.pin !== undefined &&
255
+ input.refusedPinPolicy === 'move-on-confirmed-exhaustion' &&
256
+ (input.killswitch?.get(input.pin.accountId) === false ||
257
+ pinRefusal?.reason === 'exhausted' ||
258
+ pinRefusal?.reason === 'budget-spent');
259
+ const reserveFor = (row) => {
260
+ const perRow = typeof input.rowReservePercent === 'function'
261
+ ? input.rowReservePercent(row)
262
+ : input.rowReservePercent?.get(row.id);
263
+ return perRow ?? input.reservePercent ?? {};
264
+ };
265
+ const scope = input.scope;
266
+ let candidates = input.rows
267
+ .map((row, configuredOrder) => ({ row, configuredOrder }))
268
+ .filter(({ row }) => !excluded.has(row.id))
269
+ .map(({ row, configuredOrder }) => {
270
+ const killswitchPasses = input.killswitch?.get(row.id);
271
+ const credits = input.resetCreditsApplicable?.get(row.id);
272
+ return {
273
+ accountId: row.id,
274
+ quota: row.kind === 'api-key'
275
+ ? undefined
276
+ : (admitted.get(row.id)?.projection ??
277
+ projectQuota(row.quota, scope)),
278
+ reservePercent: reserveFor(row),
279
+ configuredOrder,
280
+ ...(credits === undefined ? {} : { resetCreditsApplicable: credits }),
281
+ ...(killswitchPasses === undefined ? {} : { killswitchPasses }),
282
+ };
283
+ });
284
+ const refusedSelections = [];
285
+ const unplaced = pinValid
286
+ ? { action: 'retain' }
287
+ : input.pin
288
+ ? { action: 'clear' }
289
+ : { action: 'none' };
290
+ while (candidates.length > 0) {
291
+ const selection = selectStickyCandidate({
292
+ candidates,
293
+ pendingBytes: input.pendingBytes ?? new Map(),
294
+ requestBytes: input.requestBytes,
295
+ now: input.now,
296
+ ...(input.onEmptyWeightedSet
297
+ ? { onEmptyWeightedSet: input.onEmptyWeightedSet }
298
+ : {}),
299
+ });
300
+ if (!selection)
301
+ break;
302
+ const refusal = refusals.get(selection.accountId);
303
+ if (refusal) {
304
+ refusedSelections.push(refusal);
305
+ candidates = candidates.filter((candidate) => candidate.accountId !== selection.accountId);
306
+ continue;
307
+ }
308
+ const identity = input.identities?.get(selection.accountId);
309
+ return {
310
+ outcome: 'dispatch',
311
+ accountId: selection.accountId,
312
+ source: selection.source,
313
+ ...(selection.quotaCheckedAt === undefined
314
+ ? {}
315
+ : { quotaCheckedAt: selection.quotaCheckedAt }),
316
+ pin: pinValid && !pinMoves
317
+ ? { action: 'retain' }
318
+ : {
319
+ action: 'assign',
320
+ pin: {
321
+ accountId: selection.accountId,
322
+ inputBytes: input.requestBytes,
323
+ ...(identity === undefined ? {} : { wireIdentity: identity }),
324
+ ...(selection.quotaCheckedAt === undefined
325
+ ? {}
326
+ : { quotaCheckedAt: selection.quotaCheckedAt }),
327
+ },
328
+ },
329
+ refusedSelections,
330
+ admission,
331
+ };
332
+ }
333
+ return {
334
+ outcome: 'no-admissible-account',
335
+ pin: unplaced,
336
+ refusedSelections,
337
+ admission,
338
+ };
339
+ }