@cortexkit/common-auth 0.2.6 → 0.2.8
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.
- package/dist/claustrum/consumer.d.ts +93 -0
- package/dist/claustrum/consumer.js +276 -0
- package/dist/claustrum/custody.d.ts +128 -0
- package/dist/claustrum/custody.js +321 -0
- package/dist/claustrum/enrollment.d.ts +121 -0
- package/dist/claustrum/enrollment.js +579 -0
- package/dist/claustrum/errors.d.ts +16 -0
- package/dist/claustrum/errors.js +8 -0
- package/dist/claustrum/host-slot.d.ts +39 -0
- package/dist/claustrum/host-slot.js +72 -0
- package/dist/claustrum/index.d.ts +18 -1
- package/dist/claustrum/index.js +22 -2
- package/dist/claustrum/interlock.d.ts +29 -0
- package/dist/claustrum/interlock.js +36 -0
- package/dist/claustrum/roster.d.ts +103 -0
- package/dist/claustrum/roster.js +334 -0
- package/dist/opencode2/errors.d.ts +23 -0
- package/dist/opencode2/errors.js +15 -0
- package/dist/opencode2/index.d.ts +8 -1
- package/dist/opencode2/index.js +4 -2
- package/dist/opencode2/install.d.ts +19 -0
- package/dist/opencode2/install.js +415 -0
- package/dist/opencode2/integration.d.ts +78 -0
- package/dist/opencode2/integration.js +94 -0
- package/dist/opencode2/sse.d.ts +12 -0
- package/dist/opencode2/sse.js +60 -0
- package/dist/opencode2/types.d.ts +202 -0
- package/dist/opencode2/types.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
function parseBlock(block) {
|
|
2
|
+
const data = [];
|
|
3
|
+
let event;
|
|
4
|
+
for (const line of block.split(/\r?\n/)) {
|
|
5
|
+
if (line.startsWith('data:'))
|
|
6
|
+
data.push(line.slice(5).replace(/^ /, ''));
|
|
7
|
+
else if (line.startsWith('event:'))
|
|
8
|
+
event = line.slice(6).trim();
|
|
9
|
+
}
|
|
10
|
+
if (data.length === 0)
|
|
11
|
+
return undefined;
|
|
12
|
+
return event === undefined
|
|
13
|
+
? { data: data.join('\n') }
|
|
14
|
+
: { event, data: data.join('\n') };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A pass-through stream that hands every complete server-sent event to
|
|
18
|
+
* `onEvent` while forwarding the original bytes unchanged, so the host still
|
|
19
|
+
* consumes the body exactly once. A throwing `onEvent` is reported to
|
|
20
|
+
* `onError` and never breaks the stream.
|
|
21
|
+
*/
|
|
22
|
+
export function watchServerSentEvents(onEvent, onError) {
|
|
23
|
+
const decoder = new TextDecoder();
|
|
24
|
+
let buffer = '';
|
|
25
|
+
const emit = (block) => {
|
|
26
|
+
const event = parseBlock(block);
|
|
27
|
+
if (!event)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
onEvent(event);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
onError(error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const drain = () => {
|
|
37
|
+
for (;;) {
|
|
38
|
+
const match = /\r?\n\r?\n/.exec(buffer);
|
|
39
|
+
if (!match)
|
|
40
|
+
return;
|
|
41
|
+
const block = buffer.slice(0, match.index);
|
|
42
|
+
buffer = buffer.slice(match.index + match[0].length);
|
|
43
|
+
emit(block);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return new TransformStream({
|
|
47
|
+
transform(chunk, controller) {
|
|
48
|
+
controller.enqueue(chunk);
|
|
49
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
50
|
+
drain();
|
|
51
|
+
},
|
|
52
|
+
flush() {
|
|
53
|
+
buffer += decoder.decode();
|
|
54
|
+
drain();
|
|
55
|
+
if (buffer.trim() !== '')
|
|
56
|
+
emit(buffer);
|
|
57
|
+
buffer = '';
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Plugin } from '@opencode/plugin';
|
|
2
|
+
import type { SessionRequestKind, SessionRetryDecision } from '@opencode/plugin/promise/session';
|
|
3
|
+
/** The parts of the OpenCode 2 plugin context the installer uses. */
|
|
4
|
+
export type OpenCode2HookContext = {
|
|
5
|
+
readonly session: Pick<Plugin.Context['session'], 'hook'>;
|
|
6
|
+
/** Used only to forget per-session records when a session is deleted. */
|
|
7
|
+
readonly event?: Pick<Plugin.Context['event'], 'subscribe'>;
|
|
8
|
+
};
|
|
9
|
+
export type RequestKind = SessionRequestKind;
|
|
10
|
+
/** Which request a hook call belongs to, as the host reports it. */
|
|
11
|
+
export interface RequestScope {
|
|
12
|
+
readonly providerID: string;
|
|
13
|
+
readonly modelID: string;
|
|
14
|
+
readonly sessionID: string;
|
|
15
|
+
readonly agent: string;
|
|
16
|
+
readonly kind: RequestKind;
|
|
17
|
+
}
|
|
18
|
+
/** Where a reading or a refusal was seen. */
|
|
19
|
+
export type Transport = 'http' | 'ws';
|
|
20
|
+
/**
|
|
21
|
+
* A provider refusal that should move the request to another account: a rate
|
|
22
|
+
* limit, an exhausted usage window, or anything else the adapter decides is
|
|
23
|
+
* tied to the account rather than to the request.
|
|
24
|
+
*/
|
|
25
|
+
export interface LimitSignal {
|
|
26
|
+
/** Short machine-readable reason, such as the provider's error code. */
|
|
27
|
+
readonly reason: string;
|
|
28
|
+
readonly status?: number;
|
|
29
|
+
/** How long the provider asked the account to wait, when it said. */
|
|
30
|
+
readonly retryAfterMs?: number;
|
|
31
|
+
}
|
|
32
|
+
/** What the adapter learned from one streamed event or WebSocket frame. */
|
|
33
|
+
export interface EventVerdict<Q> {
|
|
34
|
+
/** The user has seen output from this response: a retry would repeat it. */
|
|
35
|
+
readonly outputStarted?: boolean;
|
|
36
|
+
readonly quota?: Q;
|
|
37
|
+
readonly limit?: LimitSignal;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Header changes for one account. A string sets the header (replacing every
|
|
41
|
+
* spelling of its name); `null` removes it.
|
|
42
|
+
*/
|
|
43
|
+
export type HeaderEdits = Readonly<Record<string, string | null>>;
|
|
44
|
+
export interface ChooseAccountInput extends RequestScope {
|
|
45
|
+
/** The account the previous attempt of this session and kind used. */
|
|
46
|
+
readonly previousAccountId?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Set when the previous attempt was refused for a limit before any output
|
|
49
|
+
* and the retry hook asked the host to try again. The adapter should not
|
|
50
|
+
* pick `accountId` again unless it has nothing else.
|
|
51
|
+
*/
|
|
52
|
+
readonly rerouteFrom?: {
|
|
53
|
+
readonly accountId: string;
|
|
54
|
+
readonly limit: LimitSignal;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export interface AccountRequest extends RequestScope {
|
|
58
|
+
readonly accountId: string;
|
|
59
|
+
}
|
|
60
|
+
/** A host-supplied error, as the retry hook reports it. */
|
|
61
|
+
export interface HostError {
|
|
62
|
+
readonly type: string;
|
|
63
|
+
readonly message: string;
|
|
64
|
+
readonly status?: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Everything provider-specific the installer needs. `Q` is the plugin's own
|
|
68
|
+
* quota reading type; the installer only carries it to the `quota` event.
|
|
69
|
+
*/
|
|
70
|
+
export interface OpenCode2AuthAdapter<Q = unknown> {
|
|
71
|
+
/** Every hook is scoped to this provider; other providers are untouched. */
|
|
72
|
+
readonly providerID: string;
|
|
73
|
+
/**
|
|
74
|
+
* Picks the account for one model request. Called again for every retry,
|
|
75
|
+
* so a refused account can be skipped. Returning `undefined` stops the
|
|
76
|
+
* request with `OpenCode2AuthError` kind `no-account`.
|
|
77
|
+
*/
|
|
78
|
+
chooseAccount(input: ChooseAccountInput): Promise<string | undefined> | string | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The credential and per-account headers for an account, applied in
|
|
81
|
+
* `model.request`, `http.request` and `experimental.ws.handshake`. The last
|
|
82
|
+
* two run after the host has applied its own credential, so these headers
|
|
83
|
+
* win on the wire.
|
|
84
|
+
*/
|
|
85
|
+
accountHeaders(input: AccountRequest): Promise<HeaderEdits> | HeaderEdits;
|
|
86
|
+
/**
|
|
87
|
+
* Optional request rewrite (URL, body) before the account headers are
|
|
88
|
+
* applied. Return `undefined` to keep the request.
|
|
89
|
+
*/
|
|
90
|
+
rewriteRequest?(input: AccountRequest & {
|
|
91
|
+
readonly request: Request;
|
|
92
|
+
}): Promise<Request | undefined> | Request | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Optional response rewrite (body stream, status). It receives the
|
|
95
|
+
* response after quota, limit and output detection have been attached, so
|
|
96
|
+
* detection always sees the provider's own events.
|
|
97
|
+
*/
|
|
98
|
+
rewriteResponse?(input: AccountRequest & {
|
|
99
|
+
readonly request: Request;
|
|
100
|
+
readonly response: Response;
|
|
101
|
+
}): Promise<Response | undefined> | Response | undefined;
|
|
102
|
+
/** Optional WebSocket URL rewrite. Return `undefined` to keep the URL. */
|
|
103
|
+
rewriteHandshakeURL?(input: AccountRequest & {
|
|
104
|
+
readonly url: string;
|
|
105
|
+
}): string | undefined;
|
|
106
|
+
/** Quota carried in HTTP response headers. */
|
|
107
|
+
quotaFromHeaders?(headers: Headers, status: number): Q | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Recognises an account-level refusal from an HTTP response before its
|
|
110
|
+
* body is streamed. `body()` reads a copy, so the host still gets the body.
|
|
111
|
+
*/
|
|
112
|
+
limitFromResponse?(input: {
|
|
113
|
+
readonly status: number;
|
|
114
|
+
readonly headers: Headers;
|
|
115
|
+
readonly body: () => Promise<string>;
|
|
116
|
+
}): Promise<LimitSignal | undefined> | LimitSignal | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Inspects one server-sent event (`data` payload) or one WebSocket frame.
|
|
119
|
+
* Detects output, quota and refusals inside the stream.
|
|
120
|
+
*/
|
|
121
|
+
inspectEvent?(input: {
|
|
122
|
+
readonly transport: Transport;
|
|
123
|
+
readonly data: string;
|
|
124
|
+
readonly event?: string;
|
|
125
|
+
}): EventVerdict<Q> | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* Recognises an account-level refusal from the error the host hands the
|
|
128
|
+
* retry hook, for refusals no other hook saw.
|
|
129
|
+
*/
|
|
130
|
+
limitFromError?(error: HostError): LimitSignal | undefined;
|
|
131
|
+
}
|
|
132
|
+
export interface OpenCode2AuthLogger {
|
|
133
|
+
warn(message: string, data?: unknown): void;
|
|
134
|
+
}
|
|
135
|
+
export interface InstallOpenCode2AuthOptions {
|
|
136
|
+
/**
|
|
137
|
+
* Values that must never reach the wire, normally the placeholder the host
|
|
138
|
+
* holds as its credential. Defaults to the placeholder secret for the
|
|
139
|
+
* adapter's provider.
|
|
140
|
+
*/
|
|
141
|
+
readonly hostCredentials?: readonly string[];
|
|
142
|
+
/** Most `sessionID:kind` records kept before the oldest is dropped. */
|
|
143
|
+
readonly maxRecords?: number;
|
|
144
|
+
readonly logger?: OpenCode2AuthLogger;
|
|
145
|
+
}
|
|
146
|
+
export type RetryReason = 'reroute' | 'output-started' | 'no-account' | 'host-decides';
|
|
147
|
+
/**
|
|
148
|
+
* The hook that picked an account. Normally `model.request`; a transport hook
|
|
149
|
+
* picks only when the host skipped `model.request` for that request, which a
|
|
150
|
+
* plugin may want to log as a host change.
|
|
151
|
+
*/
|
|
152
|
+
export type SelectingHook = 'model.request' | 'http.request' | 'experimental.ws.handshake';
|
|
153
|
+
export interface OpenCode2AuthEvents<Q> {
|
|
154
|
+
/** An account was picked for a model request. */
|
|
155
|
+
readonly select: AccountRequest & {
|
|
156
|
+
readonly hook: SelectingHook;
|
|
157
|
+
readonly previousAccountId?: string;
|
|
158
|
+
readonly rerouteFrom?: ChooseAccountInput['rerouteFrom'];
|
|
159
|
+
};
|
|
160
|
+
/** A quota reading, attributed through this installer's own record. */
|
|
161
|
+
readonly quota: AccountRequest & {
|
|
162
|
+
readonly transport: Transport;
|
|
163
|
+
readonly status?: number;
|
|
164
|
+
readonly quota: Q;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* An account-level refusal. The retry hook waits for every listener of
|
|
168
|
+
* this event before asking the host to retry, so a plugin that marks the
|
|
169
|
+
* account limited here has the mark in place when `chooseAccount` runs.
|
|
170
|
+
*/
|
|
171
|
+
readonly limit: AccountRequest & {
|
|
172
|
+
readonly via: Transport | 'error';
|
|
173
|
+
readonly limit: LimitSignal;
|
|
174
|
+
readonly outputStarted: boolean;
|
|
175
|
+
};
|
|
176
|
+
/** The retry hook ran for this provider. */
|
|
177
|
+
readonly retry: {
|
|
178
|
+
readonly sessionID: string;
|
|
179
|
+
readonly accountId?: string;
|
|
180
|
+
readonly kind?: RequestKind;
|
|
181
|
+
readonly attempt: number;
|
|
182
|
+
readonly reason: RetryReason;
|
|
183
|
+
readonly hostDecision: SessionRetryDecision;
|
|
184
|
+
readonly decision: SessionRetryDecision;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
export type OpenCode2AuthEventName = keyof OpenCode2AuthEvents<unknown>;
|
|
188
|
+
export interface OpenCode2AuthInstallation<Q> {
|
|
189
|
+
/**
|
|
190
|
+
* Listens to an event. Listener errors are logged and never reach the
|
|
191
|
+
* host. Returns a function that removes the listener.
|
|
192
|
+
*/
|
|
193
|
+
on<E extends OpenCode2AuthEventName>(event: E, listener: (payload: OpenCode2AuthEvents<Q>[E]) => void | Promise<void>): () => void;
|
|
194
|
+
/** The account last chosen for a session and request kind. */
|
|
195
|
+
accountFor(sessionID: string, kind: RequestKind): string | undefined;
|
|
196
|
+
/** Drops every record of a session. Session deletion does this itself. */
|
|
197
|
+
forgetSession(sessionID: string): void;
|
|
198
|
+
/** Number of `sessionID:kind` records held. */
|
|
199
|
+
readonly size: number;
|
|
200
|
+
/** Removes every hook and stops listening for session deletion. */
|
|
201
|
+
dispose(): Promise<void>;
|
|
202
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/common-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Shared code for the CortexKit auth plugins: account pool, quota and routing, commands and auth menu, OpenCode 2 hooks, Claustrum custody, and plumbing (loopback RPC, file locks, logger, sidebar state, TUI preferences and build).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|