@cortexkit/common-auth 0.2.9 → 0.4.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 (60) hide show
  1. package/dist/cachekeep/manager.d.ts +18 -6
  2. package/dist/cachekeep/manager.js +40 -10
  3. package/dist/claustrum/consumer.d.ts +13 -4
  4. package/dist/claustrum/consumer.js +11 -3
  5. package/dist/claustrum/custody.d.ts +47 -6
  6. package/dist/claustrum/custody.js +37 -7
  7. package/dist/claustrum/errors.d.ts +1 -1
  8. package/dist/claustrum/index.d.ts +3 -3
  9. package/dist/claustrum/index.js +2 -2
  10. package/dist/claustrum/interlock.d.ts +15 -17
  11. package/dist/claustrum/interlock.js +19 -26
  12. package/dist/claustrum/roster.d.ts +96 -6
  13. package/dist/claustrum/roster.js +209 -44
  14. package/dist/commands/builtins.d.ts +1 -1
  15. package/dist/commands/builtins.js +6 -1
  16. package/dist/commands/index.d.ts +2 -2
  17. package/dist/commands/index.js +1 -1
  18. package/dist/commands/menu.d.ts +8 -0
  19. package/dist/commands/menu.js +34 -13
  20. package/dist/commands/model.d.ts +9 -0
  21. package/dist/commands/seam.d.ts +40 -4
  22. package/dist/commands/seam.js +132 -19
  23. package/dist/dump/index.d.ts +94 -0
  24. package/dist/dump/index.js +236 -9
  25. package/dist/logger/engine.d.ts +52 -17
  26. package/dist/logger/engine.js +178 -135
  27. package/dist/logger/index.d.ts +2 -2
  28. package/dist/logger/index.js +1 -1
  29. package/dist/opencode2/index.d.ts +1 -1
  30. package/dist/opencode2/install.d.ts +23 -5
  31. package/dist/opencode2/install.js +335 -140
  32. package/dist/opencode2/types.d.ts +149 -9
  33. package/dist/quota/projection.d.ts +11 -4
  34. package/dist/quota/projection.js +11 -4
  35. package/dist/routing/admission.js +3 -1
  36. package/dist/routing/index.d.ts +2 -2
  37. package/dist/routing/index.js +1 -1
  38. package/dist/routing/sticky.d.ts +19 -6
  39. package/dist/routing/sticky.js +34 -23
  40. package/dist/rpc/notifications.d.ts +20 -0
  41. package/dist/rpc/notifications.js +21 -0
  42. package/dist/rpc/rpc-server.d.ts +9 -1
  43. package/dist/rpc/rpc-server.js +8 -1
  44. package/dist/sidebar-file/index.d.ts +1 -1
  45. package/dist/sidebar-file/sidebar-file.d.ts +50 -2
  46. package/dist/sidebar-file/sidebar-file.js +92 -21
  47. package/dist/store/attribution.js +11 -2
  48. package/dist/store/errors.d.ts +6 -3
  49. package/dist/store/identity.d.ts +13 -4
  50. package/dist/store/mutate.d.ts +23 -3
  51. package/dist/store/mutate.js +43 -26
  52. package/dist/store/pool.d.ts +8 -1
  53. package/dist/store/pool.js +7 -2
  54. package/dist/store/rows.d.ts +17 -4
  55. package/dist/store/rows.js +82 -33
  56. package/dist/store/schema.d.ts +57 -4
  57. package/dist/store/schema.js +100 -7
  58. package/dist/store/torn.d.ts +29 -0
  59. package/dist/store/torn.js +113 -0
  60. package/package.json +1 -1
@@ -8,53 +8,6 @@ const ORDER = {
8
8
  trace: 4,
9
9
  };
10
10
  const MAX_BYTES = 5 * 1024 * 1024;
11
- /**
12
- * Where lines are written, and the level floor, as the host supplied them.
13
- *
14
- * Both start unset. A host decides where its log lives — that decision reads
15
- * host environment variables and host directories, neither of which belongs in
16
- * shared code — and calls `initLogger` before it runs any command. Until then
17
- * every `log.*` call is a silent no-op: throwing would turn the first command a
18
- * host forgot to wire into a crash, and buffering would hold credential-bearing
19
- * lines for an init that may never arrive.
20
- */
21
- let logFileSource;
22
- let initLevelSource;
23
- let runtimeLevel;
24
- let redactor = createRedactor();
25
- let captureSink;
26
- /**
27
- * Point the logger at a host's file and level. Idempotent: calling it again
28
- * replaces both. A runtime level installed by `setLogLevel` is deliberately
29
- * left alone, because it is the operator's explicit choice and outranks the
30
- * floor a host computed at start-up.
31
- */
32
- export function initLogger(options) {
33
- logFileSource = options.file;
34
- initLevelSource = options.level;
35
- redactor = createRedactor(options);
36
- captureSink = options.captureSink;
37
- }
38
- export function setLogLevel(l) {
39
- if (l === undefined || l in ORDER)
40
- runtimeLevel = l;
41
- }
42
- function logFilePath() {
43
- if (logFileSource === undefined)
44
- return undefined;
45
- const resolved = typeof logFileSource === 'function' ? logFileSource() : logFileSource;
46
- return resolved || undefined;
47
- }
48
- function configuredLevel() {
49
- if (runtimeLevel)
50
- return runtimeLevel;
51
- const floor = typeof initLevelSource === 'function' ? initLevelSource() : initLevelSource;
52
- if (floor && floor in ORDER)
53
- return floor;
54
- return 'info';
55
- }
56
- let buffer = [];
57
- let timer;
58
11
  const ROTATE_KEEP = 3;
59
12
  function chmodPrivate(path) {
60
13
  try {
@@ -83,46 +36,6 @@ function rotateIfNeeded(f) {
83
36
  /* never throw */
84
37
  }
85
38
  }
86
- /**
87
- * Write whatever is buffered. Safe to call synchronously from a process-exit
88
- * handler, which is how each host drains the buffer on shutdown.
89
- */
90
- export function flushLogs() {
91
- if (timer) {
92
- clearTimeout(timer);
93
- timer = undefined;
94
- }
95
- if (!buffer.length)
96
- return;
97
- let file;
98
- try {
99
- file = logFilePath();
100
- }
101
- catch {
102
- buffer = [];
103
- return;
104
- }
105
- const text = buffer.join('');
106
- buffer = [];
107
- if (!file)
108
- return;
109
- try {
110
- rotateIfNeeded(file);
111
- if (existsSync(file))
112
- chmodPrivate(file);
113
- appendFileSync(file, text, { encoding: 'utf8', mode: 0o600 });
114
- }
115
- catch {
116
- /* never throw */
117
- }
118
- }
119
- function schedule() {
120
- if (!timer)
121
- timer = setTimeout(() => {
122
- timer = undefined;
123
- flushLogs();
124
- }, 500);
125
- }
126
39
  function safeSerialize(data) {
127
40
  try {
128
41
  return ` ${JSON.stringify(data)}`;
@@ -131,69 +44,199 @@ function safeSerialize(data) {
131
44
  return ' [unserializable]';
132
45
  }
133
46
  }
134
- function emit(channel, level, message, data) {
135
- if (logFileSource === undefined)
136
- return;
137
- try {
138
- if (ORDER[level] > ORDER[configuredLevel()])
47
+ /**
48
+ * One logger's settings and buffer.
49
+ *
50
+ * Where lines are written and the level floor start unset unless `options`
51
+ * supplies them. A host decides where its log lives (that decision reads host
52
+ * environment variables and host directories, neither of which belongs in
53
+ * shared code) and configures the logger before it runs any command. Until
54
+ * then every `log.*` call is a silent no-op: throwing would turn the first
55
+ * command a host forgot to wire into a crash, and buffering would hold
56
+ * credential-bearing lines for a configuration that may never arrive.
57
+ */
58
+ function createEngine(options) {
59
+ let logFileSource;
60
+ let initLevelSource;
61
+ let runtimeLevel;
62
+ let redactor = createRedactor();
63
+ let captureSink;
64
+ let buffer = [];
65
+ let timer;
66
+ function configure(next) {
67
+ logFileSource = next.file;
68
+ initLevelSource = next.level;
69
+ redactor = createRedactor(next);
70
+ captureSink = next.captureSink;
71
+ }
72
+ function setLogLevel(l) {
73
+ if (l === undefined || l in ORDER)
74
+ runtimeLevel = l;
75
+ }
76
+ function logFilePath() {
77
+ if (logFileSource === undefined)
78
+ return undefined;
79
+ const resolved = typeof logFileSource === 'function' ? logFileSource() : logFileSource;
80
+ return resolved || undefined;
81
+ }
82
+ function configuredLevel() {
83
+ if (runtimeLevel)
84
+ return runtimeLevel;
85
+ const floor = typeof initLevelSource === 'function'
86
+ ? initLevelSource()
87
+ : initLevelSource;
88
+ if (floor && floor in ORDER)
89
+ return floor;
90
+ return 'info';
91
+ }
92
+ function flushLogs() {
93
+ if (timer) {
94
+ clearTimeout(timer);
95
+ timer = undefined;
96
+ }
97
+ if (!buffer.length)
139
98
  return;
140
- const scrubbedMessage = redactor.redactStrings(message);
141
- let scrubbedData;
99
+ let file;
142
100
  try {
143
- scrubbedData = redactor.redact(data);
101
+ file = logFilePath();
144
102
  }
145
103
  catch {
146
- scrubbedData = '[unserializable]';
104
+ buffer = [];
105
+ return;
147
106
  }
148
- const line = `[${new Date().toISOString()}] ${level.toUpperCase()} [${channel}] ${scrubbedMessage}` +
149
- (data === undefined ? '' : safeSerialize(scrubbedData)) +
150
- '\n';
151
- // A failing observer must not prevent file logging or escape into the host.
107
+ const text = buffer.join('');
108
+ buffer = [];
109
+ if (!file)
110
+ return;
152
111
  try {
153
- captureSink?.({
154
- channel,
155
- level,
156
- message: scrubbedMessage,
157
- data: scrubbedData,
158
- });
112
+ rotateIfNeeded(file);
113
+ if (existsSync(file))
114
+ chmodPrivate(file);
115
+ appendFileSync(file, text, { encoding: 'utf8', mode: 0o600 });
116
+ }
117
+ catch {
118
+ /* never throw */
159
119
  }
160
- catch { }
161
- buffer.push(line);
162
- if (buffer.length >= 50)
163
- flushLogs();
164
- else
165
- schedule();
166
120
  }
167
- catch {
168
- // Provider and redaction failures must never turn diagnostics into a host crash.
121
+ function schedule() {
122
+ if (!timer)
123
+ timer = setTimeout(() => {
124
+ timer = undefined;
125
+ flushLogs();
126
+ }, 500);
169
127
  }
128
+ function emit(channel, level, message, data) {
129
+ if (logFileSource === undefined)
130
+ return;
131
+ try {
132
+ if (ORDER[level] > ORDER[configuredLevel()])
133
+ return;
134
+ const scrubbedMessage = redactor.redactStrings(message);
135
+ let scrubbedData;
136
+ try {
137
+ scrubbedData = redactor.redact(data);
138
+ }
139
+ catch {
140
+ scrubbedData = '[unserializable]';
141
+ }
142
+ const line = `[${new Date().toISOString()}] ${level.toUpperCase()} [${channel}] ${scrubbedMessage}` +
143
+ (data === undefined ? '' : safeSerialize(scrubbedData)) +
144
+ '\n';
145
+ // A failing observer must not prevent file logging or escape into the host.
146
+ try {
147
+ captureSink?.({
148
+ channel,
149
+ level,
150
+ message: scrubbedMessage,
151
+ data: scrubbedData,
152
+ });
153
+ }
154
+ catch { }
155
+ buffer.push(line);
156
+ if (buffer.length >= 50)
157
+ flushLogs();
158
+ else
159
+ schedule();
160
+ }
161
+ catch {
162
+ // Provider and redaction failures must never turn diagnostics into a host crash.
163
+ }
164
+ }
165
+ function createLogger(channel) {
166
+ return {
167
+ error: (m, d) => emit(channel, 'error', m, d),
168
+ warn: (m, d) => emit(channel, 'warn', m, d),
169
+ info: (m, d) => emit(channel, 'info', m, d),
170
+ debug: (m, d) => emit(channel, 'debug', m, d),
171
+ trace: (m, d) => emit(channel, 'trace', m, d),
172
+ };
173
+ }
174
+ function reset() {
175
+ buffer = [];
176
+ if (timer) {
177
+ clearTimeout(timer);
178
+ timer = undefined;
179
+ }
180
+ logFileSource = undefined;
181
+ initLevelSource = undefined;
182
+ runtimeLevel = undefined;
183
+ redactor = createRedactor();
184
+ captureSink = undefined;
185
+ }
186
+ if (options)
187
+ configure(options);
188
+ return { configure, createLogger, setLogLevel, flushLogs, reset };
189
+ }
190
+ /**
191
+ * A logger instance of its own, configured with `options`. A plugin whose
192
+ * copy of this module may be shared with another plugin in the same process
193
+ * uses this instead of `initLogger`, so neither replaces the other's file,
194
+ * level, redaction or capture sink.
195
+ */
196
+ export function createLoggerInstance(options) {
197
+ const { configure, createLogger, setLogLevel, flushLogs } = createEngine(options);
198
+ return { configure, createLogger, setLogLevel, flushLogs };
199
+ }
200
+ /**
201
+ * The instance behind the module-level functions below, which keep the
202
+ * single-plugin API: one plugin per copy of this module calls `initLogger`
203
+ * and `createLogger` without holding an instance.
204
+ */
205
+ const defaultEngine = createEngine();
206
+ /**
207
+ * Point the module's default logger at a host's file and level, and return
208
+ * it. Idempotent: calling it again replaces both. A runtime level installed
209
+ * by `setLogLevel` is deliberately left alone, because it is the operator's
210
+ * explicit choice and outranks the floor a host computed at start-up.
211
+ */
212
+ export function initLogger(options) {
213
+ defaultEngine.configure(options);
214
+ const { configure, createLogger, setLogLevel, flushLogs } = defaultEngine;
215
+ return { configure, createLogger, setLogLevel, flushLogs };
216
+ }
217
+ export function setLogLevel(l) {
218
+ defaultEngine.setLogLevel(l);
219
+ }
220
+ /**
221
+ * Write whatever the default logger has buffered. Safe to call synchronously
222
+ * from a process-exit handler, which is how each host drains the buffer on
223
+ * shutdown.
224
+ */
225
+ export function flushLogs() {
226
+ defaultEngine.flushLogs();
170
227
  }
171
228
  export function createLogger(channel) {
172
- return {
173
- error: (m, d) => emit(channel, 'error', m, d),
174
- warn: (m, d) => emit(channel, 'warn', m, d),
175
- info: (m, d) => emit(channel, 'info', m, d),
176
- debug: (m, d) => emit(channel, 'debug', m, d),
177
- trace: (m, d) => emit(channel, 'trace', m, d),
178
- };
229
+ return defaultEngine.createLogger(channel);
179
230
  }
180
231
  export async function flushForTest() {
181
- flushLogs();
232
+ defaultEngine.flushLogs();
182
233
  }
183
234
  /**
184
- * Return the logger to its uninitialised state. Only a test needs this: a
185
- * single process runs every test file, so a file path left over from one test
186
- * would keep a later "logger was never initialised" case writing lines.
235
+ * Return the default logger to its uninitialised state. Only a test needs
236
+ * this: a single process runs every test file, so a file path left over from
237
+ * one test would keep a later "logger was never initialised" case writing
238
+ * lines.
187
239
  */
188
240
  export function resetLoggerForTest() {
189
- buffer = [];
190
- if (timer) {
191
- clearTimeout(timer);
192
- timer = undefined;
193
- }
194
- logFileSource = undefined;
195
- initLevelSource = undefined;
196
- runtimeLevel = undefined;
197
- redactor = createRedactor();
198
- captureSink = undefined;
241
+ defaultEngine.reset();
199
242
  }
@@ -1,6 +1,6 @@
1
1
  export type { CaptureSink, LogTestRecord } from './capture-sink.js';
2
2
  export { createCaptureSink } from './capture-sink.js';
3
- export type { InitLoggerOptions, Level } from './engine.js';
4
- export { createLogger, flushForTest, flushLogs, initLogger, resetLoggerForTest, setLogLevel, } from './engine.js';
3
+ export type { ChannelLogger, InitLoggerOptions, Level, LoggerInstance, } from './engine.js';
4
+ export { createLogger, createLoggerInstance, flushForTest, flushLogs, initLogger, resetLoggerForTest, setLogLevel, } from './engine.js';
5
5
  export type { RedactionOptions, Redactor } from './redact.js';
6
6
  export { createRedactor, redact, redactStrings } from './redact.js';
@@ -1,3 +1,3 @@
1
1
  export { createCaptureSink } from './capture-sink.js';
2
- export { createLogger, flushForTest, flushLogs, initLogger, resetLoggerForTest, setLogLevel, } from './engine.js';
2
+ export { createLogger, createLoggerInstance, flushForTest, flushLogs, initLogger, resetLoggerForTest, setLogLevel, } from './engine.js';
3
3
  export { createRedactor, redact, redactStrings } from './redact.js';
@@ -5,4 +5,4 @@ export type { FormAnswer, PoolAuthorization, PoolLoginMethod, RegisterOpenCode2A
5
5
  export { isPlaceholderCredential, PLACEHOLDER_LIFETIME_MS, PLACEHOLDER_METADATA_KEY, PLACEHOLDER_PREFIX, placeholderCredential, placeholderSecret, registerOpenCode2AuthMethods, } from './integration.js';
6
6
  export type { ServerSentEvent } from './sse.js';
7
7
  export { watchServerSentEvents } from './sse.js';
8
- export type { AccountRequest, ChooseAccountInput, EventVerdict, HeaderEdits, HostError, InstallOpenCode2AuthOptions, LimitSignal, OpenCode2AuthAdapter, OpenCode2AuthEventName, OpenCode2AuthEvents, OpenCode2AuthInstallation, OpenCode2AuthLogger, OpenCode2HookContext, RequestKind, RequestScope, RetryReason, SelectingHook, Transport, } from './types.js';
8
+ export type { AccountHeadersResult, AccountRequest, Attempt, AttemptEndReason, AttemptOutcome, ChooseAccountInput, EventVerdict, HeaderEdits, HostError, InstallOpenCode2AuthOptions, LimitSignal, OpenCode2AuthAdapter, OpenCode2AuthEventName, OpenCode2AuthEvents, OpenCode2AuthInstallation, OpenCode2AuthLogger, OpenCode2HookContext, RequestKind, RequestScope, RetryReason, SelectingHook, Transport, } from './types.js';
@@ -6,14 +6,32 @@ export declare function applyHeaderEdits(target: Record<string, string>, edits:
6
6
  * Installs multi-account auth on OpenCode 2's own provider drivers. Every
7
7
  * hook is scoped to `adapter.providerID`:
8
8
  *
9
- * - `model.request` picks the account for the request's `sessionID:kind` and
10
- * sets its headers;
9
+ * - `model.request` picks the account for the request's `sessionID:kind`,
10
+ * which starts a new attempt, and sets its headers;
11
11
  * - `http.request` and `experimental.ws.handshake` set them again, because
12
12
  * the host applies its own credential after `model.request`;
13
- * - `http.response` and `experimental.ws.receive` read quota, refusals and
14
- * whether output has started, attributed through the record above;
13
+ * - `experimental.ws.send` (only when the adapter rewrites frames) rewrites
14
+ * each outgoing frame;
15
+ * - `http.response` and `experimental.ws.receive` read quota, refusals,
16
+ * whether output has started and when the response ended, attributed to
17
+ * an attempt by the rules below;
15
18
  * - `retry` asks the host to retry at once when an account was refused
16
19
  * before any output, so `model.request` runs again and can pick another
17
20
  * account, and refuses to retry once output has started.
21
+ *
22
+ * Attribution. An HTTP response belongs to the attempt whose `http.request`
23
+ * produced its request object, and to nothing else. The host hands
24
+ * `http.response` the request object the `http.request` hooks left, so a
25
+ * different object means a later hook replaced it; nothing then proves which
26
+ * send the response answers (an earlier attempt's send may still be in
27
+ * flight, and only the newest attempt of each session and kind is kept), and
28
+ * its feedback is dropped rather than guessed by recency. No marker can ride
29
+ * on the request instead: the host builds the wire request from that same
30
+ * object, so a marker would be sent to the provider. A WebSocket frame
31
+ * belongs to the newest attempt of its session and kind only while that
32
+ * attempt went out over WebSocket and has not ended: the host runs one
33
+ * exchange at a time per session socket, so frames between one handshake
34
+ * and the next belong to the earlier attempt. Anything else is attributed to
35
+ * no attempt and reaches no adapter callback or listener.
18
36
  */
19
- export declare function installOpenCode2Auth<Q>(ctx: OpenCode2HookContext, adapter: OpenCode2AuthAdapter<Q>, options?: InstallOpenCode2AuthOptions): Promise<OpenCode2AuthInstallation<Q>>;
37
+ export declare function installOpenCode2Auth<Q, A = unknown>(ctx: OpenCode2HookContext, adapter: OpenCode2AuthAdapter<Q, A>, options?: InstallOpenCode2AuthOptions): Promise<OpenCode2AuthInstallation<Q, A>>;