@cortexkit/opencode-anthropic-auth 1.22.0 → 2.0.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 +259 -198
- package/dist/billing-lineage.d.ts +51 -0
- package/dist/cli-failure.d.ts +69 -0
- package/dist/cli.d.ts +16 -10
- package/dist/cli.js +111 -6
- package/dist/custody-dimensions.d.ts +19 -0
- package/dist/custody-mode.d.ts +46 -0
- package/dist/effort-history.d.ts +11 -2
- package/dist/fable-fallback.d.ts +1 -1
- package/dist/index-1jkyhgyr.js +2 -0
- package/dist/{index-bx5hsgf8.js → index-2arfb1y3.js} +2 -2
- package/dist/{index-926t2bzc.js → index-4ya27n12.js} +1 -1
- package/dist/{index-cgez4m0h.js → index-aaajxwzd.js} +1 -1
- package/dist/index-ae3fq4n6.js +42 -0
- package/dist/index-fds0s2j4.js +2 -0
- package/dist/index-nngmvczq.js +450 -0
- package/dist/index-pn597b6f.js +2 -0
- package/dist/{index-7f57ewkp.js → index-rqync0pc.js} +1 -1
- package/dist/index.js +1 -33
- package/dist/native-command.d.ts +19 -0
- package/dist/prime-manager-registry.d.ts +5 -1
- package/dist/rpc/notifications.d.ts +4 -3
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +15 -4
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.d.ts +3 -1
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/rpc/rpc-server.d.ts +5 -1
- package/dist/rpc/server-registry.d.ts +10 -0
- package/dist/setup/activation.d.ts +7 -0
- package/dist/setup/claustrum.d.ts +26 -0
- package/dist/setup/command-runner.d.ts +2 -0
- package/dist/setup/command.d.ts +20 -0
- package/dist/setup/command.test.d.ts +1 -0
- package/dist/setup/detect.d.ts +5 -0
- package/dist/setup/jsonc.d.ts +9 -0
- package/dist/setup/native-activation.d.ts +29 -0
- package/dist/setup/native-enrollment.d.ts +26 -0
- package/dist/setup/native-enrollment.test.d.ts +1 -0
- package/dist/setup/native-local.d.ts +20 -0
- package/dist/setup/native-login.d.ts +3 -0
- package/dist/setup/native-paths.d.ts +15 -0
- package/dist/setup/native-paths.test.d.ts +1 -0
- package/dist/setup/opencode-config.d.ts +18 -0
- package/dist/setup/paths.d.ts +15 -0
- package/dist/setup/pi.d.ts +18 -0
- package/dist/setup/process-fence.d.ts +20 -0
- package/dist/setup/process-fence.test.d.ts +1 -0
- package/dist/setup/types.d.ts +37 -0
- package/dist/sidebar-state.d.ts +3 -1
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +5 -1
- package/dist/tui/command-dialogs.d.ts +32 -2
- package/dist/tui-preferences.js +2 -2
- package/dist/v2/activation.d.ts +7 -0
- package/dist/v2/adapter.d.ts +276 -0
- package/dist/v2/install.d.ts +13 -0
- package/dist/v2/lifecycle.d.ts +8 -0
- package/dist/v2/menu-contract.d.ts +8 -0
- package/dist/v2/menu.d.ts +10 -0
- package/dist/v2/models.d.ts +6 -0
- package/dist/v2/ownership.d.ts +11 -0
- package/dist/v2/prime.d.ts +12 -0
- package/dist/v2/rpc.d.ts +50 -0
- package/dist/v2/server.d.ts +8 -0
- package/dist/v2/server.js +4 -0
- package/dist/v2/setup.d.ts +58 -0
- package/dist/v2/source.d.ts +41 -0
- package/dist/v2/tui.d.ts +13 -0
- package/dist/v2/tui.js +2 -0
- package/dist/v2/wire.d.ts +36 -0
- package/package.json +26 -6
- package/src/rpc/notifications.ts +37 -26
- package/src/rpc/protocol.ts +24 -7
- package/src/rpc/rpc-client.ts +18 -1
- package/src/rpc/rpc-server.ts +38 -8
- package/src/rpc/server-registry.ts +61 -0
- package/src/sidebar-state.ts +26 -2
- package/src/tui/command-dialogs.tsx +537 -68
- package/src/tui/entry.mjs +44 -18
- package/src/tui-compiled/rpc/protocol.ts +24 -7
- package/src/tui-compiled/rpc/rpc-client.ts +18 -1
- package/src/tui-compiled/sidebar-state.ts +26 -2
- package/src/tui-compiled/tui/command-dialogs.tsx +488 -95
- package/src/tui-compiled/tui.tsx +25 -7
- package/src/tui.tsx +38 -9
- package/dist/index-8aga2zwv.js +0 -4
- package/dist/index-d3f8g6ve.js +0 -428
- package/dist/index-sh51yqxs.js +0 -2
- package/dist/logger-jrkranbz.js +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type NativeAccountRuntime, type NativeAccountSnapshot, type NativeApiSubject, type NativeLocalCredentialValidation as NativeKnownCredentialSubject, type NativePoolPaths, type OAuthQuotaSnapshot } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
import type { RequestScope } from '@cortexkit/common-auth/opencode2';
|
|
3
|
+
import { type NativeAnthropicBodyOptions, type NativeAnthropicRequestContext, type NativeAnthropicResponseContext, type NativeAnthropicResponseOptions, type NativeAnthropicSource } from './adapter';
|
|
4
|
+
/**
|
|
5
|
+
* Identifies the account and credential version that served a send, so a
|
|
6
|
+
* later 401 or quota reading is recorded against that exact version. It never
|
|
7
|
+
* holds OAuth tokens, API keys or the private vault authorization receipt.
|
|
8
|
+
*/
|
|
9
|
+
export type NativeSourceProvenance = {
|
|
10
|
+
readonly kind: 'local';
|
|
11
|
+
readonly subject: NativeKnownCredentialSubject;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'vault';
|
|
14
|
+
readonly credentialId: string;
|
|
15
|
+
readonly recordVersion: number;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: 'api';
|
|
18
|
+
readonly subject: NativeApiSubject;
|
|
19
|
+
};
|
|
20
|
+
export interface NativeSourceOptions {
|
|
21
|
+
readonly runtime: NativeAccountRuntime;
|
|
22
|
+
readonly paths: NativePoolPaths;
|
|
23
|
+
readonly requestOptions: (scope: NativeAnthropicRequestContext<NativeSourceProvenance>, snapshot: NativeAccountSnapshot) => NativeAnthropicBodyOptions;
|
|
24
|
+
readonly responseOptions?: (scope: NativeAnthropicResponseContext<NativeSourceProvenance>) => NativeAnthropicResponseOptions;
|
|
25
|
+
readonly onRequestId?: (scope: NativeAnthropicResponseContext<NativeSourceProvenance>, requestId: string) => void;
|
|
26
|
+
readonly onForgetSession?: (sessionID: string) => void;
|
|
27
|
+
readonly onOwnedRequest?: () => void;
|
|
28
|
+
readonly fetch?: typeof fetch;
|
|
29
|
+
/** Resolve a host model alias to the upstream ID before request-local policy. */
|
|
30
|
+
readonly resolveModelId?: (scope: RequestScope) => Promise<string>;
|
|
31
|
+
}
|
|
32
|
+
export interface NativeSource extends NativeAnthropicSource<OAuthQuotaSnapshot, NativeSourceProvenance> {
|
|
33
|
+
readonly close: () => Promise<void>;
|
|
34
|
+
readonly resetRouting: (sessionID: string) => Promise<void>;
|
|
35
|
+
readonly recordPrime: (provenance: NativeSourceProvenance, usage: {
|
|
36
|
+
inputTokens?: number;
|
|
37
|
+
outputTokens?: number;
|
|
38
|
+
}) => Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
/** The native account service supplies fresh credentials; existing quota and routing rules choose each OpenCode 2 request's route. */
|
|
41
|
+
export declare function createNativeAnthropicSource(options: NativeSourceOptions): NativeSource;
|
package/dist/v2/tui.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin as Tui } from '@opencode/plugin/tui';
|
|
2
|
+
/**
|
|
3
|
+
* Shows the `claude` account menu in OpenCode 2 dialogs. The menu's sections,
|
|
4
|
+
* items and actions come from the plugin server over NATIVE_MENU_RPC, built by
|
|
5
|
+
* the same Core menu code that OpenCode 1 and Pi render, so every host shows
|
|
6
|
+
* the same menu.
|
|
7
|
+
*/
|
|
8
|
+
export declare function setupAnthropicTui(ctx: Tui.Context): Promise<() => void>;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
id: string;
|
|
11
|
+
setup: typeof setupAnthropicTui;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
package/dist/v2/tui.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{s as F}from"../index-1jkyhgyr.js";import"../index-aaajxwzd.js";async function V(B){let Y=B.client.rpc(F),K=!0,$=!1,L=new AbortController,O=()=>{let w=B.ui.router.current();return w.type==="session"?w.sessionID:void 0};function M(w){return new Promise((J)=>{let j="",G=!1,Q=B.keymap.mode.push("cortexkit-protected-entry"),H=(z,D=!0)=>{if(G)return;G=!0,B.renderer.keyInput.off("keypress",X),B.renderer.keyInput.off("paste",Z),L.signal.removeEventListener("abort",W),Q();let q=z?j:void 0;if(j="",D)B.ui.dialog.clear();J(q)},W=()=>H(!1),X=(z)=>{if(z.preventDefault(),z.stopPropagation(),z.name==="escape"||z.ctrl&&z.name==="c")return H(!1);if(z.name==="return"||z.name==="enter")return H(!0);if(z.name==="backspace"){j=Array.from(j).slice(0,-1).join("");return}if(!z.ctrl&&!z.meta&&!z.option&&Array.from(z.sequence).every((D)=>D.charCodeAt(0)>=32&&D.charCodeAt(0)!==127))j=(j+z.sequence).slice(0,4096)},Z=(z)=>{z.preventDefault(),z.stopPropagation(),j=(j+Array.from(new TextDecoder().decode(z.bytes)).filter((D)=>D.charCodeAt(0)>=32&&D.charCodeAt(0)!==127).join("")).slice(0,4096)};if(B.renderer.keyInput.prependListener("keypress",X),B.renderer.keyInput.prependListener("paste",Z),L.signal.addEventListener("abort",W,{once:!0}),B.ui.dialog.alert({title:w,message:"Type or paste the value. Input is hidden. Enter accepts; Escape cancels."}).then(()=>H(!1,!1),()=>H(!1,!1)),L.signal.aborted)W()})}async function P(w){let J={};for(let j of w.knobs){if(!K)return;if(j.kind==="choice"){let G=await B.ui.dialog.select({title:j.label,options:j.choices.map((Q)=>({title:Q.label,value:Q.value})),current:j.value});if(G===void 0)return;J[j.id]=G}else if(j.kind==="toggle"){let G=await B.ui.dialog.select({title:j.label,options:[{title:"On",value:!0},{title:"Off",value:!1}],current:j.value});if(G===void 0)return;J[j.id]=G}else{let G=j.kind==="text"&&j.masked?await M(j.label):await B.ui.dialog.prompt({title:j.label,value:j.value===void 0?void 0:String(j.value),placeholder:j.kind==="text"?j.placeholder:void 0});if(G===void 0)return;J[j.id]=G===""&&!j.required?null:j.kind==="number"?Number(G):G}}return J}async function C(w,J){if(!K||$||O()!==w)return;$=!0;let j=J;try{while(K&&O()===w){let G=await B.ui.dialog.select({title:j.menu.title,options:j.menu.sections.map((D)=>({title:D.title,description:D.lines.join(`
|
|
2
|
+
`),value:D}))});if(!G||!K||O()!==w)return;let Q=[...G.actions.map((D)=>({action:D,itemId:void 0})),...G.items.flatMap((D)=>D.actions.map((q)=>({action:q,itemId:D.id})))],H=await B.ui.dialog.select({title:G.title,options:Q.map((D)=>({title:D.action.label,description:D.action.description,value:D}))});if(!H)continue;let W=await P(H.action);if(!W||!K||O()!==w)return;let X=H.action.confirm?await B.ui.dialog.confirm({title:H.action.label,message:H.action.confirm.message}):void 0;if(H.action.confirm&&X!==!0)continue;if(!K||O()!==w)return;let Z={command:"claude",sectionId:G.id,itemId:H.itemId,actionId:H.action.id,sessionId:w,values:W,confirmed:X},z=await Y.apply(Z,{signal:L.signal,location:B.location});if(!K||O()!==w)return;await B.ui.dialog.alert({title:z.ok?"Claude":"Claude action refused",message:z.text}),j={command:z.command,menu:z.menu}}}finally{$=!1}}let R=Y.events.on("opened",(w)=>C(w.data.sessionId,w.data.payload),{signal:L.signal}),U=Y.events.on("notification",(w)=>{if(K&&O()===w.data.sessionId)B.ui.toast.show({title:"Claude",sessionID:w.data.sessionId,message:w.data.message,variant:w.data.kind})},{signal:L.signal});return B.keymap.layer(()=>({commands:[{id:"cortexkit.claude",title:"Claude",group:"Claude",palette:!0,slash:{name:"claude"},async run(){let w=O();if(!w){await B.ui.dialog.alert({title:"Claude",message:"Open a session before using the Claude menu."});return}let J=await Y.open({sessionId:w},{signal:L.signal,location:B.location});await C(w,J)}}]})),()=>{K=!1,L.abort(),R(),U()}}var T={id:"cortexkit-anthropic-auth",setup:V};export{V as setupAnthropicTui,T as default};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type NativeAccountSnapshot } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
import type { Plugin } from '@opencode/plugin';
|
|
3
|
+
import type { Registration } from '@opencode/plugin/promise/registration';
|
|
4
|
+
import type { NativeAnthropicRequestContext, NativeAnthropicResponseContext } from './adapter';
|
|
5
|
+
import type { NativeSourceProvenance } from './source';
|
|
6
|
+
/**
|
|
7
|
+
* Claude Code's billing header names the user prompt a request belongs to and
|
|
8
|
+
* the provider request-id of the previous request for that prompt. Track that
|
|
9
|
+
* chain per user turn with OpenCode's message IDs: one session spans many
|
|
10
|
+
* turns, so a session ID alone cannot tell which turn a request serves.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createNativeWirePolicy(): {
|
|
13
|
+
install(ctx: Pick<Plugin.Context, 'session'>): Promise<Registration>;
|
|
14
|
+
requestOptions(input: NativeAnthropicRequestContext<NativeSourceProvenance>, snapshot: NativeAccountSnapshot): {
|
|
15
|
+
cache1hEnabled: boolean;
|
|
16
|
+
cache1hMode: "automatic" | "explicit" | "hybrid";
|
|
17
|
+
fastModeEnabled: boolean;
|
|
18
|
+
thinkingPrefixMismatchBehavior: "account-default" | "drop_block" | "error" | undefined;
|
|
19
|
+
midConversationEffortEnabled: boolean;
|
|
20
|
+
serverSideFallbackEnabled: boolean;
|
|
21
|
+
laneStart: boolean;
|
|
22
|
+
billingLineage: {
|
|
23
|
+
promptId: string;
|
|
24
|
+
previousRequestId: string | undefined;
|
|
25
|
+
} | undefined;
|
|
26
|
+
cacheDiagnosticsPreviousMessageId: string | undefined;
|
|
27
|
+
};
|
|
28
|
+
responseOptions(input: NativeAnthropicResponseContext<NativeSourceProvenance>): {
|
|
29
|
+
serverSideFallbackModel: string;
|
|
30
|
+
laneStart: boolean;
|
|
31
|
+
onMessageStart(message: Record<string, unknown>): void;
|
|
32
|
+
onStreamEnd(): void;
|
|
33
|
+
};
|
|
34
|
+
onRequestId(input: NativeAnthropicResponseContext<NativeSourceProvenance>, requestId: string): void;
|
|
35
|
+
forgetSession(sessionID: string): void;
|
|
36
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-anthropic-auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"types": "./dist/tui.d.ts",
|
|
19
19
|
"import": "./src/tui/entry.mjs"
|
|
20
20
|
},
|
|
21
|
+
"./server": {
|
|
22
|
+
"types": "./dist/v2/server.d.ts",
|
|
23
|
+
"import": "./dist/v2/server.js"
|
|
24
|
+
},
|
|
25
|
+
"./rpc": {
|
|
26
|
+
"types": "./dist/rpc/rpc-client.d.ts",
|
|
27
|
+
"import": "./dist/rpc/rpc-client.js"
|
|
28
|
+
},
|
|
21
29
|
"./tui-prefs": {
|
|
22
30
|
"types": "./dist/tui-preferences.d.ts",
|
|
23
31
|
"import": "./dist/tui-preferences.js"
|
|
@@ -45,9 +53,10 @@
|
|
|
45
53
|
"LICENSE"
|
|
46
54
|
],
|
|
47
55
|
"scripts": {
|
|
48
|
-
"build": "rm -rf dist && bun build src/index.ts src/cli.ts src/sidebar-state.ts src/tui-preferences.ts src/rpc/rpc-client.ts src/rpc/port-file.ts src/rpc/protocol.ts src/rpc/rpc-dir.ts --outdir dist --target node --format esm --splitting --external @opencode-ai/plugin --minify && tsc -p tsconfig.build.json --emitDeclarationOnly && bun run build:tui",
|
|
56
|
+
"build": "rm -rf dist && bun build src/index.ts src/v2/server.ts src/v2/tui.ts src/cli.ts src/sidebar-state.ts src/tui-preferences.ts src/rpc/rpc-client.ts src/rpc/port-file.ts src/rpc/protocol.ts src/rpc/rpc-dir.ts --outdir dist --root src --target node --format esm --splitting --external @opencode-ai/plugin --external @opencode/plugin --external jsonc-parser --minify && tsc -p tsconfig.build.json --emitDeclarationOnly && bun run build:tui && bun run check:bundle",
|
|
49
57
|
"build:tui": "bun scripts/build-tui.ts",
|
|
50
58
|
"smoke:tui": "bun scripts/smoke-tui-pack-install.ts",
|
|
59
|
+
"check:bundle": "bun scripts/check-bundle-globals.ts",
|
|
51
60
|
"build:dev": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
52
61
|
"dev": "bun ../../scripts/dev.ts",
|
|
53
62
|
"dev:clean": "bun ../../scripts/dev-clean.ts",
|
|
@@ -58,13 +67,24 @@
|
|
|
58
67
|
"prepublishOnly": "bun run build"
|
|
59
68
|
},
|
|
60
69
|
"peerDependencies": {
|
|
61
|
-
"@opencode-ai/plugin": "*"
|
|
70
|
+
"@opencode-ai/plugin": "*",
|
|
71
|
+
"@opencode/plugin": ">=2.0.21"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@opencode/plugin": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
62
77
|
},
|
|
63
78
|
"dependencies": {
|
|
64
|
-
"@
|
|
65
|
-
"@
|
|
66
|
-
"@opentui/
|
|
79
|
+
"@clack/prompts": "^1.6.0",
|
|
80
|
+
"@cortexkit/anthropic-auth-core": "2.0.0",
|
|
81
|
+
"@opentui/core": "0.5.11",
|
|
82
|
+
"@opentui/solid": "0.5.11",
|
|
67
83
|
"jsonc-parser": "^3.3.1",
|
|
68
84
|
"solid-js": "1.9.12"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@ai-sdk/anthropic": "4.0.58",
|
|
88
|
+
"@opencode/plugin": "2.0.22"
|
|
69
89
|
}
|
|
70
90
|
}
|
package/src/rpc/notifications.ts
CHANGED
|
@@ -1,54 +1,65 @@
|
|
|
1
|
+
import type { CommandDialogPayload } from '@cortexkit/anthropic-auth-core'
|
|
1
2
|
import type { OpenDialogPayload, RpcNotification } from './protocol'
|
|
2
3
|
|
|
3
4
|
const QUEUE_CAP = 100
|
|
4
5
|
const TUI_CONNECTED_WINDOW_MS = 3_000
|
|
5
6
|
|
|
7
|
+
// One queue serves every RPC server in the process, and a process can hold one
|
|
8
|
+
// server per project directory. Session ids are globally unique, so both
|
|
9
|
+
// producers and drains require one: an unscoped drain could otherwise expose
|
|
10
|
+
// another project's pending command dialog.
|
|
6
11
|
let queue: RpcNotification[] = []
|
|
7
12
|
let nextId = 1
|
|
8
|
-
let lastDrainAtAny = 0
|
|
9
13
|
const lastDrainAtBySession = new Map<string, number>()
|
|
10
14
|
|
|
15
|
+
function assertSessionId(sessionId: string): void {
|
|
16
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0) {
|
|
17
|
+
throw new TypeError('sessionId is required')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
export function pushNotification(
|
|
12
|
-
payload: OpenDialogPayload,
|
|
13
|
-
sessionId
|
|
22
|
+
payload: OpenDialogPayload | CommandDialogPayload,
|
|
23
|
+
sessionId: string,
|
|
14
24
|
): void {
|
|
15
|
-
|
|
25
|
+
assertSessionId(sessionId)
|
|
26
|
+
if ('menu' in payload) {
|
|
27
|
+
if (payload.command !== 'claude' || payload.menu.command !== 'claude')
|
|
28
|
+
throw new TypeError('Only the claude menu is supported')
|
|
29
|
+
queue.push({ id: nextId++, type: 'open-menu', payload, sessionId })
|
|
30
|
+
} else {
|
|
31
|
+
queue.push({ id: nextId++, type: 'open-dialog', payload, sessionId })
|
|
32
|
+
}
|
|
16
33
|
if (queue.length > QUEUE_CAP) queue = queue.slice(queue.length - QUEUE_CAP)
|
|
17
34
|
}
|
|
18
35
|
|
|
19
36
|
export function drainNotifications(
|
|
20
|
-
lastReceivedId
|
|
21
|
-
sessionId
|
|
37
|
+
lastReceivedId: number,
|
|
38
|
+
sessionId: string,
|
|
22
39
|
): RpcNotification[] {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (sessionId !== undefined) lastDrainAtBySession.set(sessionId, now)
|
|
26
|
-
const matches = (n: RpcNotification) =>
|
|
27
|
-
sessionId === undefined ||
|
|
28
|
-
n.sessionId === undefined ||
|
|
29
|
-
n.sessionId === sessionId
|
|
40
|
+
assertSessionId(sessionId)
|
|
41
|
+
lastDrainAtBySession.set(sessionId, Date.now())
|
|
30
42
|
if (lastReceivedId > 0) {
|
|
31
|
-
queue = queue.filter(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
queue = queue.filter(
|
|
44
|
+
(notification) =>
|
|
45
|
+
notification.id > lastReceivedId ||
|
|
46
|
+
notification.sessionId !== sessionId,
|
|
47
|
+
)
|
|
36
48
|
}
|
|
37
|
-
return queue.filter(
|
|
49
|
+
return queue.filter(
|
|
50
|
+
(notification) =>
|
|
51
|
+
notification.id > lastReceivedId && notification.sessionId === sessionId,
|
|
52
|
+
)
|
|
38
53
|
}
|
|
39
54
|
|
|
40
|
-
export function isTuiConnected(sessionId
|
|
55
|
+
export function isTuiConnected(sessionId: string): boolean {
|
|
41
56
|
const now = Date.now()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS
|
|
45
|
-
}
|
|
46
|
-
return lastDrainAtAny > 0 && now - lastDrainAtAny < TUI_CONNECTED_WINDOW_MS
|
|
57
|
+
const at = lastDrainAtBySession.get(sessionId) ?? 0
|
|
58
|
+
return at > 0 && now - at < TUI_CONNECTED_WINDOW_MS
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
export function resetNotificationsForTest(): void {
|
|
50
62
|
queue = []
|
|
51
63
|
nextId = 1
|
|
52
|
-
lastDrainAtAny = 0
|
|
53
64
|
lastDrainAtBySession.clear()
|
|
54
65
|
}
|
package/src/rpc/protocol.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ClaustrumEnrollmentStatus,
|
|
3
|
+
CommandDialogPayload,
|
|
4
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
5
|
+
|
|
1
6
|
export const COMMAND_MODAL_NAMES = [
|
|
2
7
|
'claude-account',
|
|
3
8
|
'claude-cache',
|
|
@@ -17,17 +22,31 @@ export type CommandModalName = (typeof COMMAND_MODAL_NAMES)[number]
|
|
|
17
22
|
export interface AccountDialogAccount {
|
|
18
23
|
id: string
|
|
19
24
|
label: string
|
|
20
|
-
role:
|
|
25
|
+
role: 'main' | 'fallback'
|
|
21
26
|
enabled: boolean
|
|
22
27
|
quotaPercent: number | null
|
|
23
28
|
tierLabel?: string
|
|
24
29
|
claustrumGate: 'on' | 'off' | 'na'
|
|
25
30
|
vaultServed: boolean
|
|
31
|
+
vaultReauth: boolean
|
|
32
|
+
custodyState:
|
|
33
|
+
| 'na'
|
|
34
|
+
| 'off'
|
|
35
|
+
| 'on-vault-served'
|
|
36
|
+
| 'on-vault-reauth'
|
|
37
|
+
| 'on-cold'
|
|
38
|
+
| 'unknown-identity'
|
|
39
|
+
| 'on-identity-mismatch'
|
|
40
|
+
| 'on-corrupt-binding'
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
export interface AccountDialogKnobs {
|
|
29
44
|
accounts: AccountDialogAccount[]
|
|
30
45
|
claustrumDetection: string
|
|
46
|
+
custodyMode?: 'local' | 'claustrum'
|
|
47
|
+
custodyModeKnown?: boolean
|
|
48
|
+
enrollmentStatus?: string
|
|
49
|
+
claustrumEnrollment?: ClaustrumEnrollmentStatus
|
|
31
50
|
[key: string]: unknown
|
|
32
51
|
}
|
|
33
52
|
|
|
@@ -37,12 +56,10 @@ export interface OpenDialogPayload {
|
|
|
37
56
|
knobs: Record<string, unknown>
|
|
38
57
|
}
|
|
39
58
|
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
type: 'open-
|
|
43
|
-
|
|
44
|
-
sessionId?: string
|
|
45
|
-
}
|
|
59
|
+
export type RpcNotification = { id: number; sessionId: string } & (
|
|
60
|
+
| { type: 'open-dialog'; payload: OpenDialogPayload }
|
|
61
|
+
| { type: 'open-menu'; payload: CommandDialogPayload }
|
|
62
|
+
)
|
|
46
63
|
|
|
47
64
|
export interface ApplyRequest {
|
|
48
65
|
command: CommandModalName
|
package/src/rpc/rpc-client.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CommandApplyRequest,
|
|
3
|
+
CommandApplyResult,
|
|
4
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
1
5
|
import { discoverPortFile } from './port-file'
|
|
2
6
|
import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
|
|
3
7
|
|
|
4
8
|
export interface RpcClient {
|
|
5
9
|
pending: (
|
|
6
10
|
lastReceivedId: number,
|
|
7
|
-
sessionId
|
|
11
|
+
sessionId: string,
|
|
8
12
|
) => Promise<RpcNotification[]>
|
|
9
13
|
apply: (request: ApplyRequest) => Promise<ApplyResult>
|
|
14
|
+
applyMenu: (request: CommandApplyRequest) => Promise<CommandApplyResult>
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
async function call<T>(
|
|
@@ -58,5 +63,17 @@ export function createRpcClient(dir: string, expectedPid?: number): RpcClient {
|
|
|
58
63
|
)
|
|
59
64
|
return out ?? { text: 'apply failed', knobs: {} }
|
|
60
65
|
},
|
|
66
|
+
async applyMenu(request) {
|
|
67
|
+
if (request.command !== 'claude' || !request.sessionId?.trim())
|
|
68
|
+
throw new TypeError('A claude menu sessionId is required')
|
|
69
|
+
const out = await call<CommandApplyResult>(
|
|
70
|
+
dir,
|
|
71
|
+
'apply-menu',
|
|
72
|
+
{ ...request },
|
|
73
|
+
expectedPid,
|
|
74
|
+
)
|
|
75
|
+
if (!out) throw new Error('Menu apply unavailable; outcome unknown')
|
|
76
|
+
return out
|
|
77
|
+
},
|
|
61
78
|
}
|
|
62
79
|
}
|
package/src/rpc/rpc-server.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from 'node:crypto'
|
|
2
|
-
import { unlink } from 'node:fs/promises'
|
|
2
|
+
import { readFile, unlink } from 'node:fs/promises'
|
|
3
3
|
import {
|
|
4
4
|
createServer,
|
|
5
5
|
type IncomingMessage,
|
|
6
6
|
type ServerResponse,
|
|
7
7
|
} from 'node:http'
|
|
8
8
|
import { join } from 'node:path'
|
|
9
|
+
import {
|
|
10
|
+
type CommandApplyRequest,
|
|
11
|
+
type CommandApplyResult,
|
|
12
|
+
parseApplyRequest,
|
|
13
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
9
14
|
import type { drainNotifications } from './notifications'
|
|
10
15
|
import { writePortFile } from './port-file'
|
|
11
16
|
import type { ApplyRequest, ApplyResult } from './protocol'
|
|
@@ -19,7 +24,10 @@ export interface RpcServerHandle {
|
|
|
19
24
|
export interface RpcServerOptions {
|
|
20
25
|
dir: string
|
|
21
26
|
drain: typeof drainNotifications
|
|
22
|
-
|
|
27
|
+
/** Structured-menu hosts need no text parser; internal text consumers may still supply one. */
|
|
28
|
+
apply?: (request: ApplyRequest) => Promise<ApplyResult>
|
|
29
|
+
/** Run menu actions through the host's credential service, not legacy account-file writes. */
|
|
30
|
+
applyMenu?: (request: CommandApplyRequest) => Promise<CommandApplyResult>
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
function readBody(req: IncomingMessage): Promise<string> {
|
|
@@ -74,21 +82,36 @@ export async function startRpcServer(
|
|
|
74
82
|
unknown
|
|
75
83
|
>
|
|
76
84
|
if (method === 'pending-notifications') {
|
|
85
|
+
if (
|
|
86
|
+
typeof params.sessionId !== 'string' ||
|
|
87
|
+
params.sessionId.length === 0
|
|
88
|
+
) {
|
|
89
|
+
return json(400, { error: 'sessionId is required' })
|
|
90
|
+
}
|
|
77
91
|
const messages = options.drain(
|
|
78
92
|
Number(params.lastReceivedId ?? 0),
|
|
79
|
-
|
|
93
|
+
params.sessionId,
|
|
80
94
|
)
|
|
81
95
|
return json(200, { messages })
|
|
82
96
|
}
|
|
83
97
|
if (method === 'apply') {
|
|
98
|
+
if (!options.apply)
|
|
99
|
+
return json(501, { error: 'text apply unavailable' })
|
|
84
100
|
const result = await options.apply(params as unknown as ApplyRequest)
|
|
85
101
|
return json(200, result)
|
|
86
102
|
}
|
|
103
|
+
if (method === 'apply-menu') {
|
|
104
|
+
const request = parseApplyRequest(params)
|
|
105
|
+
if (request?.command !== 'claude' || !request.sessionId?.trim())
|
|
106
|
+
return json(400, { error: 'invalid menu request' })
|
|
107
|
+
if (!options.applyMenu) return json(501, { error: 'menu unavailable' })
|
|
108
|
+
return json(200, await options.applyMenu(request))
|
|
109
|
+
}
|
|
87
110
|
return json(404, { error: 'unknown method' })
|
|
88
|
-
} catch
|
|
111
|
+
} catch {
|
|
89
112
|
if (!res.headersSent && !res.writableEnded && !res.destroyed) {
|
|
90
113
|
json(500, {
|
|
91
|
-
error:
|
|
114
|
+
error: 'RPC request failed',
|
|
92
115
|
})
|
|
93
116
|
}
|
|
94
117
|
}
|
|
@@ -115,9 +138,16 @@ export async function startRpcServer(
|
|
|
115
138
|
token,
|
|
116
139
|
async stop() {
|
|
117
140
|
await new Promise<void>((resolve) => server.close(() => resolve()))
|
|
118
|
-
|
|
119
|
-
(
|
|
120
|
-
|
|
141
|
+
try {
|
|
142
|
+
const portFile = join(options.dir, `port-${process.pid}.json`)
|
|
143
|
+
const current = JSON.parse(await readFile(portFile, 'utf8')) as {
|
|
144
|
+
port?: unknown
|
|
145
|
+
pid?: unknown
|
|
146
|
+
}
|
|
147
|
+
if (current.port === port && current.pid === process.pid) {
|
|
148
|
+
await unlink(portFile)
|
|
149
|
+
}
|
|
150
|
+
} catch {}
|
|
121
151
|
},
|
|
122
152
|
}
|
|
123
153
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { RpcServerHandle } from './rpc-server'
|
|
2
|
+
|
|
3
|
+
type RpcServerGlobal = typeof globalThis & {
|
|
4
|
+
__anthropicAuthRpcServers?: Map<string, RpcServerHandle>
|
|
5
|
+
__anthropicAuthRpcServersPending?: Map<string, Promise<void>>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RpcServerAdoption {
|
|
9
|
+
server: RpcServerHandle
|
|
10
|
+
release: () => Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Serializes same-directory replacement and returns an identity-fenced lease.
|
|
15
|
+
* Distinct project directories remain independent.
|
|
16
|
+
*/
|
|
17
|
+
export async function adoptRpcServer(
|
|
18
|
+
rpcDir: string,
|
|
19
|
+
create: () => Promise<RpcServerHandle>,
|
|
20
|
+
): Promise<RpcServerAdoption> {
|
|
21
|
+
const rpcGlobal = globalThis as RpcServerGlobal
|
|
22
|
+
const servers =
|
|
23
|
+
rpcGlobal.__anthropicAuthRpcServers ?? new Map<string, RpcServerHandle>()
|
|
24
|
+
const pending =
|
|
25
|
+
rpcGlobal.__anthropicAuthRpcServersPending ??
|
|
26
|
+
new Map<string, Promise<void>>()
|
|
27
|
+
rpcGlobal.__anthropicAuthRpcServers = servers
|
|
28
|
+
rpcGlobal.__anthropicAuthRpcServersPending = pending
|
|
29
|
+
|
|
30
|
+
const predecessor = pending.get(rpcDir) ?? Promise.resolve()
|
|
31
|
+
let server: RpcServerHandle | undefined
|
|
32
|
+
const start = predecessor
|
|
33
|
+
.catch(() => {})
|
|
34
|
+
.then(async () => {
|
|
35
|
+
const previous = servers.get(rpcDir)
|
|
36
|
+
if (previous) {
|
|
37
|
+
await previous.stop()
|
|
38
|
+
if (servers.get(rpcDir) === previous) servers.delete(rpcDir)
|
|
39
|
+
}
|
|
40
|
+
server = await create()
|
|
41
|
+
servers.set(rpcDir, server)
|
|
42
|
+
})
|
|
43
|
+
pending.set(rpcDir, start)
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await start
|
|
47
|
+
} finally {
|
|
48
|
+
if (pending.get(rpcDir) === start) pending.delete(rpcDir)
|
|
49
|
+
}
|
|
50
|
+
const adoptedServer = server
|
|
51
|
+
if (!adoptedServer) throw new Error('RPC server failed to start')
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
server: adoptedServer,
|
|
55
|
+
release: async () => {
|
|
56
|
+
if (servers.get(rpcDir) !== adoptedServer) return
|
|
57
|
+
await adoptedServer.stop()
|
|
58
|
+
if (servers.get(rpcDir) === adoptedServer) servers.delete(rpcDir)
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/sidebar-state.ts
CHANGED
|
@@ -36,6 +36,16 @@ export interface SidebarAccountState {
|
|
|
36
36
|
needsReauth: boolean
|
|
37
37
|
// True when the vault copy needs re-importing while the sidecar remains usable.
|
|
38
38
|
vaultReauth?: boolean
|
|
39
|
+
// A gate alone does not prove the sidecar can serve a request.
|
|
40
|
+
vaultServed?: boolean
|
|
41
|
+
custodyState?:
|
|
42
|
+
| 'off'
|
|
43
|
+
| 'on-vault-served'
|
|
44
|
+
| 'on-vault-reauth'
|
|
45
|
+
| 'on-cold'
|
|
46
|
+
| 'unknown-identity'
|
|
47
|
+
| 'on-identity-mismatch'
|
|
48
|
+
| 'on-corrupt-binding'
|
|
39
49
|
tierLabel?: string
|
|
40
50
|
}
|
|
41
51
|
|
|
@@ -59,7 +69,7 @@ export interface PrimeSidebarAccountState {
|
|
|
59
69
|
label: string
|
|
60
70
|
nextDueAt?: number | null
|
|
61
71
|
lastPrimedAt?: number | null
|
|
62
|
-
lastResult?: 'ok' | 'error'
|
|
72
|
+
lastResult?: 'ok' | 'error' | 'skipped'
|
|
63
73
|
usage?: PrimeUsageCounters
|
|
64
74
|
estimatedCostUsd?: number
|
|
65
75
|
}
|
|
@@ -180,7 +190,11 @@ function normalizePrimeAccount(
|
|
|
180
190
|
} else if (isFiniteNumber(value.lastPrimedAt)) {
|
|
181
191
|
account.lastPrimedAt = value.lastPrimedAt
|
|
182
192
|
}
|
|
183
|
-
if (
|
|
193
|
+
if (
|
|
194
|
+
value.lastResult === 'ok' ||
|
|
195
|
+
value.lastResult === 'error' ||
|
|
196
|
+
value.lastResult === 'skipped'
|
|
197
|
+
) {
|
|
184
198
|
account.lastResult = value.lastResult
|
|
185
199
|
}
|
|
186
200
|
const usage = normalizePrimeUsage(value.usage)
|
|
@@ -338,6 +352,11 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
338
352
|
needsReauth:
|
|
339
353
|
typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
|
|
340
354
|
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
355
|
+
...(entry.vaultServed === true && { vaultServed: true }),
|
|
356
|
+
...(typeof entry.custodyState === 'string' && {
|
|
357
|
+
custodyState:
|
|
358
|
+
entry.custodyState as SidebarAccountState['custodyState'],
|
|
359
|
+
}),
|
|
341
360
|
tierLabel:
|
|
342
361
|
typeof entry.tierLabel === 'string' && entry.tierLabel.trim()
|
|
343
362
|
? entry.tierLabel.trim()
|
|
@@ -817,6 +836,8 @@ export function formatFallbackModelLabel(modelId: string | undefined): string {
|
|
|
817
836
|
return 'Fable 5.1'
|
|
818
837
|
if (modelId === 'claude-fable-5' || modelId?.startsWith('claude-fable-5-'))
|
|
819
838
|
return 'Fable 5'
|
|
839
|
+
if (modelId === 'claude-opus-5-5' || modelId?.startsWith('claude-opus-5-5-'))
|
|
840
|
+
return 'Opus 5.5'
|
|
820
841
|
if (modelId === 'claude-opus-5' || modelId?.startsWith('claude-opus-5-'))
|
|
821
842
|
return 'Opus 5'
|
|
822
843
|
if (modelId === 'claude-opus-4-8' || modelId?.startsWith('claude-opus-4-8-'))
|
|
@@ -844,6 +865,9 @@ export function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
|
|
|
844
865
|
if (account.lastResult === 'error') {
|
|
845
866
|
return { text: 'err', hasError: true }
|
|
846
867
|
}
|
|
868
|
+
if (account.lastResult === 'skipped') {
|
|
869
|
+
return { text: 'skip', hasError: false }
|
|
870
|
+
}
|
|
847
871
|
if (account.nextDueAt && account.nextDueAt > Date.now()) {
|
|
848
872
|
return { text: formatPrimeTime(account.nextDueAt), hasError: false }
|
|
849
873
|
}
|