@cortexkit/opencode-openai-auth 0.7.2 → 0.8.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.
- package/README.md +9 -5
- package/dist/{index-9jpc8yct.js → index-02h7g5f9.js} +1 -1
- package/dist/index-2xppkvkx.js +2 -0
- package/dist/index-3w4vmckx.js +2 -0
- package/dist/{index-zxqb5ydj.js → index-507jh8bv.js} +1 -1
- package/dist/{index-zg1x8sty.js → index-944gsj1s.js} +1 -1
- package/dist/index-nshbgkz1.js +198 -0
- package/dist/{index-87145jkw.js → index-xyg86jgt.js} +1 -1
- package/dist/index.d.ts +381 -35
- package/dist/index.js +66 -161
- package/dist/protocol-Cv9ZmU0p.d.ts +24 -0
- package/dist/quota-normalize-wap0rne8.js +1 -0
- package/dist/rpc/port-file.d.ts +11 -9
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -21
- package/dist/rpc/rpc-client.d.ts +6 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +7 -5
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state.d.ts +84 -83
- package/dist/sidebar-state.js +1 -1
- package/dist/{token-bvp0y166.js → token-jdvpwk60.js} +1 -1
- package/dist/token-util-zzm7paz9.js +1 -0
- package/dist/tui-preferences.d.ts +27 -26
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +18 -17
- package/package.json +7 -14
- package/src/tui/entry.mjs +2 -2
- package/src/tui-compiled/{core/account-paths.ts → raw/core-account-paths.ts} +29 -18
- package/src/tui-compiled/raw/logger.ts +53 -0
- package/src/tui-compiled/{rpc/port-file.ts → raw/rpc-port-file.ts} +1 -1
- package/src/tui-compiled/raw/rpc-protocol.ts +12 -0
- package/src/{rpc/rpc-client.ts → tui-compiled/raw/rpc-rpc-client.ts} +2 -2
- package/src/{sidebar-state.ts → tui-compiled/raw/sidebar-state.ts} +2 -2
- package/src/{tui/command-dialogs.tsx → tui-compiled/raw/tui-command-dialogs.tsx} +5 -5
- package/src/{tui.tsx → tui-compiled/raw/tui.tsx} +11 -10
- package/src/{core/account-paths.ts → tui-compiled/runtime/core-account-paths.ts} +29 -18
- package/src/tui-compiled/runtime/logger.ts +53 -0
- package/src/{rpc/port-file.ts → tui-compiled/runtime/rpc-port-file.ts} +1 -1
- package/src/tui-compiled/runtime/rpc-protocol.ts +12 -0
- package/src/tui-compiled/{rpc/rpc-client.ts → runtime/rpc-rpc-client.ts} +2 -2
- package/src/tui-compiled/{sidebar-state.ts → runtime/sidebar-state.ts} +2 -2
- package/src/tui-compiled/{tui/command-dialogs.tsx → runtime/tui-command-dialogs.tsx} +3 -4
- package/src/tui-compiled/{tui.tsx → runtime/tui.tsx} +10 -10
- package/src/tui-compiled/shared/internal.ts +6 -0
- package/src/{logger.ts → tui-compiled/shared/logger.ts} +82 -21
- package/src/tui-compiled/shared/paths.ts +33 -0
- package/src/{core → tui-compiled/shared}/refresh-file-lock.ts +7 -4
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -15
- package/dist/codex-http.d.ts +0 -3
- package/dist/commands.d.ts +0 -88
- package/dist/config.d.ts +0 -47
- package/dist/core/account-paths.d.ts +0 -25
- package/dist/core/accounts.d.ts +0 -330
- package/dist/core/atomic-write.d.ts +0 -1
- package/dist/core/background-quota-refresh.d.ts +0 -46
- package/dist/core/backoff.d.ts +0 -53
- package/dist/core/cachekeep.d.ts +0 -118
- package/dist/core/oauth.d.ts +0 -138
- package/dist/core/provider.d.ts +0 -70
- package/dist/core/quota-manager.d.ts +0 -192
- package/dist/core/refresh-all-quota.d.ts +0 -86
- package/dist/core/refresh-file-lock.d.ts +0 -12
- package/dist/core/reset-credits.d.ts +0 -116
- package/dist/core/sticky-routing.d.ts +0 -43
- package/dist/dump.d.ts +0 -20
- package/dist/hosted-web-search.d.ts +0 -5
- package/dist/index-8f1200qx.js +0 -6
- package/dist/index-cfjed1st.js +0 -89
- package/dist/index-nrvjr97q.js +0 -3
- package/dist/index-qaggaze1.js +0 -2
- package/dist/index-sdcpzbnk.js +0 -2
- package/dist/logger.d.ts +0 -22
- package/dist/model-costs.d.ts +0 -29
- package/dist/prompt-context.d.ts +0 -19
- package/dist/quota-normalize-dvwtmy7f.js +0 -1
- package/dist/quota-normalize.d.ts +0 -43
- package/dist/raw-ws-bun.d.ts +0 -40
- package/dist/raw-ws-node.d.ts +0 -37
- package/dist/raw-ws-upgrade.d.ts +0 -2
- package/dist/raw-ws.d.ts +0 -3
- package/dist/response-stream-error.d.ts +0 -12
- package/dist/rpc/notifications.d.ts +0 -5
- package/dist/rpc/rpc-server.d.ts +0 -17
- package/dist/token-util-jcj7k7pa.js +0 -1
- package/dist/tui/command-dialogs.d.ts +0 -21
- package/dist/util/error.d.ts +0 -1
- package/dist/util/open-url.d.ts +0 -6
- package/dist/util/proxy-env.d.ts +0 -4
- package/dist/util/record.d.ts +0 -1
- package/dist/util/stable-json.d.ts +0 -1
- package/dist/util/uuid-v7.d.ts +0 -1
- package/dist/version.d.ts +0 -1
- package/dist/ws-pool.d.ts +0 -57
- package/dist/ws.d.ts +0 -71
- package/src/rpc/notifications.ts +0 -54
- package/src/rpc/protocol.ts +0 -33
- package/src/rpc/rpc-server.ts +0 -172
- package/src/tui-compiled/core/refresh-file-lock.ts +0 -381
- package/src/tui-compiled/logger.ts +0 -196
- package/src/tui-compiled/rpc/protocol.ts +0 -33
- package/src/util/error.ts +0 -9
- package/src/util/open-url.ts +0 -30
- package/src/util/proxy-env.ts +0 -42
- package/src/util/record.ts +0 -3
- package/src/util/stable-json.ts +0 -15
- package/src/util/uuid-v7.ts +0 -18
- /package/src/{rpc/rpc-dir.ts → tui-compiled/raw/rpc-rpc-dir.ts} +0 -0
- /package/src/tui-compiled/{tui-preferences.ts → raw/tui-preferences.ts} +0 -0
- /package/src/tui-compiled/{rpc/rpc-dir.ts → runtime/rpc-rpc-dir.ts} +0 -0
- /package/src/{tui-preferences.ts → tui-compiled/runtime/tui-preferences.ts} +0 -0
- /package/src/tui-compiled/{util → shared/util}/error.ts +0 -0
- /package/src/tui-compiled/{util → shared/util}/open-url.ts +0 -0
package/dist/raw-ws-node.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
type Listener = (event: unknown) => void;
|
|
2
|
-
export declare class RawWebSocket {
|
|
3
|
-
url: string;
|
|
4
|
-
readyState: number;
|
|
5
|
-
private socket;
|
|
6
|
-
private readonly sessionID;
|
|
7
|
-
private listeners;
|
|
8
|
-
private rxBuffer;
|
|
9
|
-
private handshakeDone;
|
|
10
|
-
private upgradeFailureEmitted;
|
|
11
|
-
private expectedAccept;
|
|
12
|
-
private fragOpcode;
|
|
13
|
-
private fragChunks;
|
|
14
|
-
constructor(url: string, headers: Record<string, string>, options?: {
|
|
15
|
-
sessionID?: string;
|
|
16
|
-
});
|
|
17
|
-
addEventListener(type: string, fn: Listener, _opts?: {
|
|
18
|
-
once?: boolean;
|
|
19
|
-
}): void;
|
|
20
|
-
removeEventListener(type: string, fn: Listener): void;
|
|
21
|
-
/** Close code and reason from the peer's close frame, if one arrived. */
|
|
22
|
-
private peerClose;
|
|
23
|
-
private closeEmitted;
|
|
24
|
-
/** Emit at most one close event, so the first reported code is the one kept. */
|
|
25
|
-
private emitClose;
|
|
26
|
-
private emit;
|
|
27
|
-
private connect;
|
|
28
|
-
private onData;
|
|
29
|
-
private emitRejectedUpgrade;
|
|
30
|
-
private drainFrames;
|
|
31
|
-
private handleFrame;
|
|
32
|
-
send(data: string): void;
|
|
33
|
-
private writeFrame;
|
|
34
|
-
close(): void;
|
|
35
|
-
private log;
|
|
36
|
-
}
|
|
37
|
-
export {};
|
package/dist/raw-ws-upgrade.d.ts
DELETED
package/dist/raw-ws.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { APICallError } from 'ai';
|
|
2
|
-
export declare class ResponseStreamError extends APICallError {
|
|
3
|
-
readonly name = "ProviderResponseStreamError";
|
|
4
|
-
/**
|
|
5
|
-
* `retryable` defaults to true because most stream failures are transient.
|
|
6
|
-
* Pass false for a failure the same request will hit again — resending then
|
|
7
|
-
* only repeats the cost and the wait.
|
|
8
|
-
*/
|
|
9
|
-
constructor(message: string, options?: ErrorOptions & {
|
|
10
|
-
retryable?: boolean;
|
|
11
|
-
});
|
|
12
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { OpenDialogPayload, RpcNotification } from './protocol';
|
|
2
|
-
export declare function pushNotification(payload: OpenDialogPayload, sessionId?: string): void;
|
|
3
|
-
export declare function drainNotifications(lastReceivedId?: number, sessionId?: string): RpcNotification[];
|
|
4
|
-
export declare function isTuiConnected(sessionId?: string): boolean;
|
|
5
|
-
export declare function resetNotificationsForTest(): void;
|
package/dist/rpc/rpc-server.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { drainNotifications } from './notifications';
|
|
2
|
-
import type { ApplyRequest, ApplyResult } from './protocol';
|
|
3
|
-
export interface RpcServerHandle {
|
|
4
|
-
port: number;
|
|
5
|
-
token: string;
|
|
6
|
-
stop: () => Promise<void>;
|
|
7
|
-
}
|
|
8
|
-
export interface RpcServerOptions {
|
|
9
|
-
dir: string;
|
|
10
|
-
secureDir?: boolean;
|
|
11
|
-
sweepRoot?: string;
|
|
12
|
-
drain: typeof drainNotifications;
|
|
13
|
-
apply: (request: ApplyRequest) => Promise<ApplyResult>;
|
|
14
|
-
timeoutMs?: number;
|
|
15
|
-
receiptTimeoutMs?: number;
|
|
16
|
-
}
|
|
17
|
-
export declare function startRpcServer(options: RpcServerOptions): Promise<RpcServerHandle>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as a}from"./index-9jpc8yct.js";import"./index-zxqb5ydj.js";export default a();
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import type { TuiPluginApi } from '@opencode-ai/plugin/tui';
|
|
3
|
-
import type { OpenDialogPayload } from '../rpc/protocol.js';
|
|
4
|
-
import { type AccountQuota, type SidebarState } from '../sidebar-state.js';
|
|
5
|
-
type ApplyFn = (command: OpenDialogPayload['command'], args: string) => Promise<{
|
|
6
|
-
text: string;
|
|
7
|
-
knobs: Record<string, unknown>;
|
|
8
|
-
}>;
|
|
9
|
-
export declare function buildCachekeepDialogOptions(payload: OpenDialogPayload): {
|
|
10
|
-
title: string;
|
|
11
|
-
value: string;
|
|
12
|
-
description: string;
|
|
13
|
-
}[];
|
|
14
|
-
export declare function openCommandDialog(api: TuiPluginApi, payload: OpenDialogPayload, apply: ApplyFn, sessionId?: string): void;
|
|
15
|
-
export declare function formatQuotaWindows(quota: AccountQuota | null | undefined): string;
|
|
16
|
-
export declare function buildAccountDialogRows(state: SidebarState, sessionId?: string): {
|
|
17
|
-
title: string;
|
|
18
|
-
value: string;
|
|
19
|
-
description: string;
|
|
20
|
-
}[];
|
|
21
|
-
export {};
|
package/dist/util/error.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function errorMessage(error: unknown): string;
|
package/dist/util/open-url.d.ts
DELETED
package/dist/util/proxy-env.d.ts
DELETED
package/dist/util/record.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function isRecord(input: unknown): input is Record<string, unknown>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function stableStringify(value: unknown): string;
|
package/dist/util/uuid-v7.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function uuidV7(): string;
|
package/dist/version.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const PackageVersion = "0.7.2";
|
package/dist/ws-pool.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export declare const TITLE_HEADER = "x-opencode-title";
|
|
2
|
-
export declare const QUOTA_ACCOUNT_HEADER = "x-openai-auth-quota-account";
|
|
3
|
-
export interface CreateWebSocketFetchOptions {
|
|
4
|
-
httpFetch?: typeof globalThis.fetch;
|
|
5
|
-
url?: string;
|
|
6
|
-
connectTimeout?: number;
|
|
7
|
-
idleTimeout?: number;
|
|
8
|
-
maxConnectionAge?: number;
|
|
9
|
-
streamRetries?: number;
|
|
10
|
-
/** Milliseconds to wait for an immediate provider-side WS error before returning response headers. */
|
|
11
|
-
firstEventGraceMs?: number;
|
|
12
|
-
/** Use the hand-rolled raw TCP/TLS WebSocket client instead of native WebSocket. */
|
|
13
|
-
rawWebSocket?: boolean;
|
|
14
|
-
/**
|
|
15
|
-
* Push per-turn quota from a codex.rate_limits in-band frame.
|
|
16
|
-
*
|
|
17
|
-
* Receives the snapshot plus the per-request account identity (access token,
|
|
18
|
-
* internal quota account key, and served ChatGPT account id) captured at send
|
|
19
|
-
* time so the frame is attributed to the connection's own account, not a
|
|
20
|
-
* shared mutable global.
|
|
21
|
-
*/
|
|
22
|
-
onQuota?: (s: Record<string, unknown>, accessToken: string, accountId: string | undefined, servedChatgptAccountId: string | undefined) => void;
|
|
23
|
-
/** Receives quota exhaustion plus the account identity captured at send time. */
|
|
24
|
-
onRateLimitReached?: (window: string, accountId: string | undefined, resetAt?: number) => void;
|
|
25
|
-
}
|
|
26
|
-
interface PoolEntry {
|
|
27
|
-
socket?: WebSocket;
|
|
28
|
-
connectedAt?: number;
|
|
29
|
-
lastUsedAt: number;
|
|
30
|
-
busy: boolean;
|
|
31
|
-
fallback: boolean;
|
|
32
|
-
streamFailures: number;
|
|
33
|
-
continuation?: ContinuationState;
|
|
34
|
-
turnID?: string;
|
|
35
|
-
turnStartedAt?: number;
|
|
36
|
-
turnInput?: unknown[];
|
|
37
|
-
turnSignature?: string;
|
|
38
|
-
}
|
|
39
|
-
interface ContinuationState {
|
|
40
|
-
responseID: string;
|
|
41
|
-
input: unknown[];
|
|
42
|
-
signature: string;
|
|
43
|
-
finalizedCallIds: Set<string>;
|
|
44
|
-
}
|
|
45
|
-
export declare function createWebSocketFetch(options?: CreateWebSocketFetchOptions): ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>) & {
|
|
46
|
-
close: () => void;
|
|
47
|
-
remove: (sessionID: string) => void;
|
|
48
|
-
};
|
|
49
|
-
export declare const CODEX_BODY_KEY_ORDER: string[];
|
|
50
|
-
export declare function orderCodexBody(body: Record<string, unknown>): Record<string, unknown>;
|
|
51
|
-
export declare function advanceTurn(entry: PoolEntry, body: Record<string, unknown>, fullBody?: Record<string, unknown>): void;
|
|
52
|
-
export declare function applyTurnId(entry: PoolEntry, body: Record<string, unknown>, fullBody?: Record<string, unknown>): Record<string, unknown>;
|
|
53
|
-
export { hasWebSocketResponsesLiteMetadata, sanitizeHttpFallbackBody, } from './codex-http';
|
|
54
|
-
export declare function withoutInternalHeaders<T extends {
|
|
55
|
-
headers?: HeadersInit;
|
|
56
|
-
}>(init: T | undefined): T | undefined;
|
|
57
|
-
export * as OpenAIWebSocketPool from './ws-pool';
|
package/dist/ws.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { ResponseStreamError } from './response-stream-error';
|
|
2
|
-
export declare const PROTOCOL_HEADER = "responses_websockets=2026-02-06";
|
|
3
|
-
export interface ConnectResponsesWebSocketOptions {
|
|
4
|
-
url: string;
|
|
5
|
-
headers: Record<string, string>;
|
|
6
|
-
timeout?: number;
|
|
7
|
-
/** Use the hand-rolled raw TCP/TLS WebSocket client instead of native WebSocket. */
|
|
8
|
-
rawWebSocket?: boolean;
|
|
9
|
-
signal?: AbortSignal;
|
|
10
|
-
}
|
|
11
|
-
export interface StreamResponsesWebSocketOptions {
|
|
12
|
-
socket: WebSocket;
|
|
13
|
-
body: Record<string, unknown>;
|
|
14
|
-
sessionID?: string;
|
|
15
|
-
idleTimeout?: number;
|
|
16
|
-
signal?: AbortSignal;
|
|
17
|
-
onFirstEvent?: (error?: WrappedError) => void;
|
|
18
|
-
/**
|
|
19
|
-
* Fires on response.completed/response.done. `finalizedFunctionCallIds` is the
|
|
20
|
-
* set of function/custom tool call ids the response actually finalized (emitted
|
|
21
|
-
* a response.output_item.done for). The pool uses it to decide which suffix
|
|
22
|
-
* function_call items are safe to trim from a continuation: only those present
|
|
23
|
-
* in the chained response may be dropped — an unfinalized call (e.g. an aborted
|
|
24
|
-
* partial) must be kept inline or its function_call_output orphans → 400.
|
|
25
|
-
*/
|
|
26
|
-
onComplete?: (event: Record<string, unknown>, finalizedFunctionCallIds: Set<string>) => void;
|
|
27
|
-
onTerminal?: (event: Record<string, unknown>) => void;
|
|
28
|
-
onRetryableTerminal?: (event: Record<string, unknown>) => Promise<WebSocket | undefined>;
|
|
29
|
-
onConnectionInvalid?: (error: ResponseStreamError) => void;
|
|
30
|
-
onAbort?: (error: Error) => void;
|
|
31
|
-
/** Push per-turn quota from a codex.rate_limits in-band frame. */
|
|
32
|
-
onQuota?: (s: Record<string, unknown>) => void;
|
|
33
|
-
/** Called when the transport reports quota exhaustion for the current connection. */
|
|
34
|
-
onRateLimitReached?: (window: string, resetAt?: number) => void;
|
|
35
|
-
}
|
|
36
|
-
export interface WrappedError {
|
|
37
|
-
status: number;
|
|
38
|
-
headers?: Record<string, string>;
|
|
39
|
-
body: string;
|
|
40
|
-
}
|
|
41
|
-
export declare function toWebSocketUrl(url: string): string;
|
|
42
|
-
export declare function normalizeHeaders(headers: HeadersInit | undefined): Record<string, string>;
|
|
43
|
-
export declare function isAbortError(error: unknown): error is DOMException;
|
|
44
|
-
export declare function isUpgradeFailure(error: unknown): boolean;
|
|
45
|
-
export declare function isOversizedFrame(error: unknown): boolean;
|
|
46
|
-
export declare function markOversizedFrame<T extends object>(error: T): T;
|
|
47
|
-
export declare function connectResponsesWebSocket(options: ConnectResponsesWebSocketOptions): Promise<WebSocket>;
|
|
48
|
-
export declare function streamResponsesWebSocket(options: StreamResponsesWebSocketOptions): Response;
|
|
49
|
-
export declare function parseRateLimitSignal(value: unknown): {
|
|
50
|
-
window: string;
|
|
51
|
-
resetAt?: number;
|
|
52
|
-
} | undefined;
|
|
53
|
-
/**
|
|
54
|
-
* Surfaced when a stream dies after output has reached the reader.
|
|
55
|
-
*
|
|
56
|
-
* Must not contain any substring the host reads as retryable — no provider
|
|
57
|
-
* wording, no response ids, no byte counts. `ws-pool.test.ts` pins it against
|
|
58
|
-
* the host's pattern set.
|
|
59
|
-
*/
|
|
60
|
-
export declare const TERMINAL_AFTER_OUTPUT_MESSAGE = "The response ended early after part of it had already been shown. It was not sent again, because repeating it would duplicate that output and re-run any tools it had started. The transport log records what ended it.";
|
|
61
|
-
/**
|
|
62
|
-
* Surfaced when the peer refuses the request frame as too large (close 1009).
|
|
63
|
-
*
|
|
64
|
-
* Fixed text for the same reason as {@link TERMINAL_AFTER_OUTPUT_MESSAGE}: the
|
|
65
|
-
* host can override this error's non-retryable flag by matching the message,
|
|
66
|
-
* and resending a frame the peer has already measured and refused is the one
|
|
67
|
-
* outcome this path exists to prevent. The size and the peer's reason are
|
|
68
|
-
* logged instead.
|
|
69
|
-
*/
|
|
70
|
-
export declare const OVERSIZED_FRAME_MESSAGE = "The request was larger than the websocket would carry, so it was sent over HTTP instead. Resending it unchanged over the same socket cannot succeed. The transport log records the size.";
|
|
71
|
-
export * as OpenAIWebSocket from './ws';
|
package/src/rpc/notifications.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { OpenDialogPayload, RpcNotification } from './protocol'
|
|
2
|
-
|
|
3
|
-
const QUEUE_CAP = 100
|
|
4
|
-
const TUI_CONNECTED_WINDOW_MS = 3_000
|
|
5
|
-
|
|
6
|
-
let queue: RpcNotification[] = []
|
|
7
|
-
let nextId = 1
|
|
8
|
-
let lastDrainAtAny = 0
|
|
9
|
-
const lastDrainAtBySession = new Map<string, number>()
|
|
10
|
-
|
|
11
|
-
export function pushNotification(
|
|
12
|
-
payload: OpenDialogPayload,
|
|
13
|
-
sessionId?: string,
|
|
14
|
-
): void {
|
|
15
|
-
queue.push({ id: nextId++, type: 'open-dialog', payload, sessionId })
|
|
16
|
-
if (queue.length > QUEUE_CAP) queue = queue.slice(queue.length - QUEUE_CAP)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function drainNotifications(
|
|
20
|
-
lastReceivedId = 0,
|
|
21
|
-
sessionId?: string,
|
|
22
|
-
): RpcNotification[] {
|
|
23
|
-
const now = Date.now()
|
|
24
|
-
lastDrainAtAny = now
|
|
25
|
-
if (sessionId !== undefined) lastDrainAtBySession.set(sessionId, now)
|
|
26
|
-
const matches = (n: RpcNotification) =>
|
|
27
|
-
sessionId === undefined ||
|
|
28
|
-
n.sessionId === undefined ||
|
|
29
|
-
n.sessionId === sessionId
|
|
30
|
-
if (lastReceivedId > 0) {
|
|
31
|
-
queue = queue.filter((n) => {
|
|
32
|
-
if (n.id > lastReceivedId) return true
|
|
33
|
-
if (sessionId === undefined) return false
|
|
34
|
-
return n.sessionId !== sessionId
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
return queue.filter((n) => n.id > lastReceivedId && matches(n))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function isTuiConnected(sessionId?: string): boolean {
|
|
41
|
-
const now = Date.now()
|
|
42
|
-
if (sessionId !== undefined) {
|
|
43
|
-
const at = lastDrainAtBySession.get(sessionId) ?? 0
|
|
44
|
-
return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS
|
|
45
|
-
}
|
|
46
|
-
return lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function resetNotificationsForTest(): void {
|
|
50
|
-
queue = []
|
|
51
|
-
nextId = 1
|
|
52
|
-
lastDrainAtAny = 0
|
|
53
|
-
lastDrainAtBySession.clear()
|
|
54
|
-
}
|
package/src/rpc/protocol.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export type CommandModalName =
|
|
2
|
-
| 'openai-quota'
|
|
3
|
-
| 'openai-account'
|
|
4
|
-
| 'openai-routing'
|
|
5
|
-
| 'openai-killswitch'
|
|
6
|
-
| 'openai-dump'
|
|
7
|
-
| 'openai-logging'
|
|
8
|
-
| 'openai-cachekeep'
|
|
9
|
-
| 'openai-reset'
|
|
10
|
-
|
|
11
|
-
export interface OpenDialogPayload {
|
|
12
|
-
command: CommandModalName
|
|
13
|
-
text: string
|
|
14
|
-
knobs: Record<string, unknown>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface RpcNotification {
|
|
18
|
-
id: number
|
|
19
|
-
type: 'open-dialog'
|
|
20
|
-
payload: OpenDialogPayload
|
|
21
|
-
sessionId?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ApplyRequest {
|
|
25
|
-
command: CommandModalName
|
|
26
|
-
arguments: string
|
|
27
|
-
sessionId?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface ApplyResult {
|
|
31
|
-
text: string
|
|
32
|
-
knobs: Record<string, unknown>
|
|
33
|
-
}
|
package/src/rpc/rpc-server.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { randomBytes, timingSafeEqual } from 'node:crypto'
|
|
2
|
-
import { unlink } from 'node:fs/promises'
|
|
3
|
-
import {
|
|
4
|
-
createServer,
|
|
5
|
-
type IncomingMessage,
|
|
6
|
-
type ServerResponse,
|
|
7
|
-
} from 'node:http'
|
|
8
|
-
import { join } from 'node:path'
|
|
9
|
-
import { createLogger } from '../logger'
|
|
10
|
-
import type { drainNotifications } from './notifications'
|
|
11
|
-
import { sweepRpcState, writePortFile } from './port-file'
|
|
12
|
-
import type { ApplyRequest, ApplyResult } from './protocol'
|
|
13
|
-
|
|
14
|
-
const log = createLogger('rpc')
|
|
15
|
-
|
|
16
|
-
export interface RpcServerHandle {
|
|
17
|
-
port: number
|
|
18
|
-
token: string
|
|
19
|
-
stop: () => Promise<void>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface RpcServerOptions {
|
|
23
|
-
dir: string
|
|
24
|
-
secureDir?: boolean
|
|
25
|
-
sweepRoot?: string
|
|
26
|
-
drain: typeof drainNotifications
|
|
27
|
-
apply: (request: ApplyRequest) => Promise<ApplyResult>
|
|
28
|
-
// Bounds handler execution via the socket inactivity timer.
|
|
29
|
-
timeoutMs?: number
|
|
30
|
-
// Bounds request delivery only (requestTimeout/headersTimeout).
|
|
31
|
-
receiptTimeoutMs?: number
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function readBody(req: IncomingMessage): Promise<string> {
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
36
|
-
const chunks: Buffer[] = []
|
|
37
|
-
let size = 0
|
|
38
|
-
req.on('data', (chunk: Buffer) => {
|
|
39
|
-
size += chunk.length
|
|
40
|
-
if (size > 1_000_000) {
|
|
41
|
-
req.destroy()
|
|
42
|
-
reject(new Error('body too large'))
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
chunks.push(chunk)
|
|
46
|
-
})
|
|
47
|
-
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
|
|
48
|
-
req.on('error', reject)
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function tokenOk(header: string | undefined, token: string): boolean {
|
|
53
|
-
if (!header?.startsWith('Bearer ')) return false
|
|
54
|
-
const got = Buffer.from(header.slice(7))
|
|
55
|
-
const want = Buffer.from(token)
|
|
56
|
-
return got.length === want.length && timingSafeEqual(got, want)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function startRpcServer(
|
|
60
|
-
options: RpcServerOptions,
|
|
61
|
-
): Promise<RpcServerHandle> {
|
|
62
|
-
const token = randomBytes(32).toString('hex')
|
|
63
|
-
// Two different bounds, deliberately not one value.
|
|
64
|
-
//
|
|
65
|
-
// requestTimeout/headersTimeout bound how long a client may take to DELIVER
|
|
66
|
-
// a request; they do not bound handler execution. A receipt bound of 500ms
|
|
67
|
-
// still returns 200 for a 3s handler. Over loopback with a 1 MiB body cap,
|
|
68
|
-
// seconds is already generous.
|
|
69
|
-
//
|
|
70
|
-
// The inactivity timer is the one that can kill a working handler: it
|
|
71
|
-
// destroys the socket out from under it. It must therefore outlast the
|
|
72
|
-
// slowest legitimate apply — a reset redemption is bounded by a 60s consume
|
|
73
|
-
// call — or the response is lost server-side no matter what deadline the
|
|
74
|
-
// client passed. Fast commands respond in milliseconds regardless.
|
|
75
|
-
//
|
|
76
|
-
// Collapsing these back into a single value reintroduces one of two bugs:
|
|
77
|
-
// a receipt bound that aborts a slow reset, or a socket timer that leaves
|
|
78
|
-
// every endpoint holding a dead connection for 90s.
|
|
79
|
-
const handlerTimeoutMs = options.timeoutMs ?? 90_000
|
|
80
|
-
const receiptTimeoutMs = options.receiptTimeoutMs ?? 2_000
|
|
81
|
-
const server = createServer((req, res) => {
|
|
82
|
-
req.setTimeout(handlerTimeoutMs, () => {
|
|
83
|
-
req.socket.destroy()
|
|
84
|
-
})
|
|
85
|
-
void dispatch(req, res)
|
|
86
|
-
})
|
|
87
|
-
server.requestTimeout = receiptTimeoutMs
|
|
88
|
-
server.headersTimeout = receiptTimeoutMs
|
|
89
|
-
|
|
90
|
-
async function dispatch(req: IncomingMessage, res: ServerResponse) {
|
|
91
|
-
const json = (status: number, value: unknown) => {
|
|
92
|
-
// Guard against writing to a socket that was destroyed (e.g. when
|
|
93
|
-
// readBody rejected after req.destroy() on an oversized body).
|
|
94
|
-
if (res.headersSent || res.writableEnded || res.destroyed) return
|
|
95
|
-
res.writeHead(status, { 'content-type': 'application/json' })
|
|
96
|
-
res.end(JSON.stringify(value))
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
const url = req.url ?? ''
|
|
100
|
-
if (req.method === 'GET' && url === '/health')
|
|
101
|
-
return json(200, { ok: true })
|
|
102
|
-
if (req.method !== 'POST' || !url.startsWith('/rpc/'))
|
|
103
|
-
return json(404, { error: 'not found' })
|
|
104
|
-
if (!tokenOk(req.headers.authorization, token))
|
|
105
|
-
return json(401, { error: 'unauthorized' })
|
|
106
|
-
const method = url.slice('/rpc/'.length)
|
|
107
|
-
const body = await readBody(req)
|
|
108
|
-
const params = JSON.parse(body || '{}') as Record<string, unknown>
|
|
109
|
-
if (method === 'pending-notifications') {
|
|
110
|
-
const messages = options.drain(
|
|
111
|
-
Number(params.lastReceivedId ?? 0),
|
|
112
|
-
typeof params.sessionId === 'string' ? params.sessionId : undefined,
|
|
113
|
-
)
|
|
114
|
-
return json(200, { messages })
|
|
115
|
-
}
|
|
116
|
-
if (method === 'apply') {
|
|
117
|
-
const result = await options.apply(params as unknown as ApplyRequest)
|
|
118
|
-
return json(200, result)
|
|
119
|
-
}
|
|
120
|
-
return json(404, { error: 'unknown method' })
|
|
121
|
-
} catch (error) {
|
|
122
|
-
json(500, {
|
|
123
|
-
error: error instanceof Error ? error.message : String(error),
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const port = await new Promise<number>((resolve, reject) => {
|
|
129
|
-
server.on('error', reject)
|
|
130
|
-
server.listen(0, '127.0.0.1', () => {
|
|
131
|
-
const addr = server.address()
|
|
132
|
-
if (addr && typeof addr === 'object') resolve(addr.port)
|
|
133
|
-
else reject(new Error('no port'))
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
server.unref()
|
|
137
|
-
if (options.sweepRoot) {
|
|
138
|
-
try {
|
|
139
|
-
await sweepRpcState(options.sweepRoot, options.dir)
|
|
140
|
-
} catch (error) {
|
|
141
|
-
log.warn('rpc state sweep failed', {
|
|
142
|
-
pid: process.pid,
|
|
143
|
-
error: error instanceof Error ? error.message : String(error),
|
|
144
|
-
})
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
try {
|
|
148
|
-
await writePortFile(
|
|
149
|
-
options.dir,
|
|
150
|
-
{ port, token, pid: process.pid },
|
|
151
|
-
{ secureDir: options.secureDir },
|
|
152
|
-
)
|
|
153
|
-
log.debug('rpc server pid', {
|
|
154
|
-
pid: process.pid,
|
|
155
|
-
rpcPort: port,
|
|
156
|
-
})
|
|
157
|
-
} catch (error) {
|
|
158
|
-
await new Promise<void>((resolve) => server.close(() => resolve()))
|
|
159
|
-
throw error
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
port,
|
|
164
|
-
token,
|
|
165
|
-
async stop() {
|
|
166
|
-
await new Promise<void>((resolve) => server.close(() => resolve()))
|
|
167
|
-
await unlink(join(options.dir, `port-${process.pid}.json`)).catch(
|
|
168
|
-
() => {},
|
|
169
|
-
)
|
|
170
|
-
},
|
|
171
|
-
}
|
|
172
|
-
}
|