@code-yeongyu/senpi-ai 2026.10.30-2 → 2026.10.31-2

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 (49) hide show
  1. package/dist/api/anthropic-messages.d.ts +13 -3
  2. package/dist/api/anthropic-messages.js +62 -185
  3. package/dist/api/anthropic-system-blocks.d.ts +12 -0
  4. package/dist/api/anthropic-system-blocks.js +35 -0
  5. package/dist/api/openai-completions.js +19 -2
  6. package/dist/auth/helpers.d.ts +1 -0
  7. package/dist/auth/helpers.js +1 -0
  8. package/dist/auth/oauth/load.d.ts +0 -2
  9. package/dist/auth/oauth/load.js +0 -5
  10. package/dist/auth/pool/slots.d.ts +11 -1
  11. package/dist/auth/pool/slots.js +63 -1
  12. package/dist/auth/types.d.ts +7 -0
  13. package/dist/bun-oauth.js +0 -2
  14. package/dist/compat/extension-oauth-types.d.ts +1 -0
  15. package/dist/models.js +1 -1
  16. package/dist/oauth.d.ts +1 -1
  17. package/dist/oauth.js +1 -1
  18. package/dist/providers/anthropic.js +2 -1
  19. package/dist/providers/chatgpt-subscription.js +2 -0
  20. package/dist/providers/data/.manifest.json +1 -1
  21. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  22. package/dist/providers/data/opengateway.json +1 -1
  23. package/dist/providers/data/openrouter.json +1 -1
  24. package/dist/providers/data/venice.json +1 -1
  25. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  26. package/dist/types.d.ts +16 -0
  27. package/dist/utils/chatgpt-subscription-auth.d.ts +7 -0
  28. package/dist/utils/chatgpt-subscription-auth.js +21 -4
  29. package/dist/utils/prompt-cache-ttl.d.ts +2 -1
  30. package/dist/utils/prompt-cache-ttl.js +2 -0
  31. package/dist/utils/retry-hint.d.ts +3 -1
  32. package/dist/utils/retry-hint.js +80 -2
  33. package/dist/utils/tool-schema-compat.js +35 -0
  34. package/dist/utils/transcript.d.ts +1 -1
  35. package/dist/utils/transcript.js +12 -2
  36. package/dist/utils/validation.js +111 -4
  37. package/package.json +2 -2
  38. package/dist/auth/oauth/anthropic-callback-listener.d.ts +0 -40
  39. package/dist/auth/oauth/anthropic-callback-listener.js +0 -138
  40. package/dist/auth/oauth/anthropic.d.ts +0 -13
  41. package/dist/auth/oauth/anthropic.js +0 -285
  42. package/dist/auth/oauth/authorization-input.d.ts +0 -6
  43. package/dist/auth/oauth/authorization-input.js +0 -29
  44. package/dist/auth/oauth/error-details.d.ts +0 -3
  45. package/dist/auth/oauth/error-details.js +0 -20
  46. package/dist/utils/claude-code-version-cache.d.ts +0 -12
  47. package/dist/utils/claude-code-version-cache.js +0 -60
  48. package/dist/utils/claude-code-version.d.ts +0 -78
  49. package/dist/utils/claude-code-version.js +0 -203
@@ -1,138 +0,0 @@
1
- /**
2
- * Loopback callback listener for the Anthropic OAuth flow.
3
- *
4
- * The OAuth client accepts any `localhost` port on the `/callback` path (the
5
- * Claude Code CLI itself binds a random port per login), so the listener
6
- * prefers the historical port 53692 and falls back to an ephemeral port when
7
- * another login - in this process or in another one - still holds it. With a
8
- * fixed port, a second login on the same machine sent its browser redirect to
9
- * the OTHER process's listener, which could only answer "State mismatch".
10
- *
11
- * NOTE: Node-only (http.createServer); reached through the lazy OAuth loader.
12
- */
13
- import { oauthErrorHtml, oauthSuccessHtml } from "../../utils/oauth-page.js";
14
- import { formatErrorDetails } from "./error-details.js";
15
- export const PREFERRED_CALLBACK_PORT = 53692;
16
- export const CALLBACK_PATH = "/callback";
17
- const BIND_FAILURE_CODES = new Set(["EACCES", "EADDRINUSE", "EPERM"]);
18
- export function callbackRedirectUri(port) {
19
- return `http://localhost:${port}${CALLBACK_PATH}`;
20
- }
21
- function respondHtml(res, status, html) {
22
- res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
23
- res.end(html);
24
- }
25
- /**
26
- * A callback whose `state` belongs to another login. Either a different
27
- * session's login could not bind its own port and is waiting for a pasted
28
- * redirect URL, or this tab belongs to an attempt that no longer exists.
29
- */
30
- function foreignLoginHtml(requestUrl) {
31
- return oauthErrorHtml("This browser login belongs to a different session of this app, or to an earlier login attempt.", [
32
- "If a session is still waiting for this login (it shows a prompt to paste the redirect URL), copy the full address from the browser's address bar and paste it there.",
33
- "Otherwise this attempt is stale: close this tab and run the login again from the session that needs it.",
34
- "",
35
- requestUrl,
36
- ].join("\n"));
37
- }
38
- function handleCallback(req, res, port, expectedState, settle) {
39
- try {
40
- const url = new URL(req.url ?? "", `http://localhost:${port}`);
41
- if (url.pathname !== CALLBACK_PATH) {
42
- respondHtml(res, 404, oauthErrorHtml("Callback route not found."));
43
- return;
44
- }
45
- const code = url.searchParams.get("code");
46
- const state = url.searchParams.get("state");
47
- const error = url.searchParams.get("error");
48
- if (error) {
49
- respondHtml(res, 400, oauthErrorHtml("Anthropic authentication did not complete.", `Error: ${error}`));
50
- return;
51
- }
52
- if (!code || !state) {
53
- respondHtml(res, 400, oauthErrorHtml("Missing code or state parameter."));
54
- return;
55
- }
56
- if (state !== expectedState) {
57
- respondHtml(res, 400, foreignLoginHtml(url.toString()));
58
- return;
59
- }
60
- respondHtml(res, 200, oauthSuccessHtml("Anthropic authentication completed. You can close this window."));
61
- settle({ code, state });
62
- }
63
- catch {
64
- res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
65
- res.end("Internal error");
66
- }
67
- }
68
- function errorCode(error) {
69
- const code = error?.code;
70
- return typeof code === "string" ? code : undefined;
71
- }
72
- function boundPort(server) {
73
- const address = server.address();
74
- return typeof address === "object" && address !== null ? address.port : undefined;
75
- }
76
- function closeServer(server) {
77
- return new Promise((resolve) => {
78
- server.close(() => resolve());
79
- // Keep-alive connections from the browser would otherwise delay the close
80
- // callback until they idle out.
81
- server.closeAllConnections?.();
82
- });
83
- }
84
- function bind(apis, port, host, expectedState, settle) {
85
- return new Promise((resolve, reject) => {
86
- const server = apis.createServer((req, res) => {
87
- handleCallback(req, res, boundPort(server) ?? port, expectedState, settle);
88
- });
89
- server.on("error", reject);
90
- server.listen(port, host, () => resolve(server));
91
- });
92
- }
93
- /**
94
- * Binds the preferred port, then an ephemeral one; when neither can be bound
95
- * the login continues in manual mode with the registered preferred-port
96
- * redirect URI, so a pasted redirect URL still exchanges.
97
- */
98
- export async function startCallbackListener(apis, expectedState, host) {
99
- let settled = false;
100
- let settle;
101
- const waitForCodePromise = new Promise((resolve) => {
102
- settle = (value) => {
103
- if (settled)
104
- return;
105
- settled = true;
106
- resolve(value);
107
- };
108
- });
109
- let lastBindFailure;
110
- for (const port of [PREFERRED_CALLBACK_PORT, 0]) {
111
- try {
112
- const server = await bind(apis, port, host, expectedState, settle);
113
- const actualPort = boundPort(server) ?? port;
114
- return {
115
- port: actualPort,
116
- redirectUri: callbackRedirectUri(actualPort),
117
- waitForCode: () => waitForCodePromise,
118
- cancelWait: () => settle(null),
119
- close: () => closeServer(server),
120
- };
121
- }
122
- catch (error) {
123
- const code = errorCode(error);
124
- if (code === undefined || !BIND_FAILURE_CODES.has(code)) {
125
- throw new Error(`Could not open OAuth callback listener at ${host}:${port}: ${formatErrorDetails(error)}`);
126
- }
127
- lastBindFailure = code;
128
- }
129
- }
130
- return {
131
- redirectUri: callbackRedirectUri(PREFERRED_CALLBACK_PORT),
132
- callbackUnavailable: { code: lastBindFailure ?? "EADDRINUSE" },
133
- waitForCode: async () => null,
134
- cancelWait: () => { },
135
- close: async () => { },
136
- };
137
- }
138
- //# sourceMappingURL=anthropic-callback-listener.js.map
@@ -1,13 +0,0 @@
1
- /**
2
- * Anthropic OAuth flow (Claude Pro/Max)
3
- *
4
- * NOTE: This module uses Node.js http.createServer for the OAuth callback server.
5
- * It is only intended for CLI use, not browser environments.
6
- */
7
- import type { OAuthAuth } from "../types.ts";
8
- import { type CallbackListenerApis } from "./anthropic-callback-listener.ts";
9
- export declare function __setAnthropicOAuthNodeApisForTests(apis: NodeApis | null): void;
10
- type NodeApis = CallbackListenerApis;
11
- export declare const anthropicOAuth: OAuthAuth;
12
- export {};
13
- //# sourceMappingURL=anthropic.d.ts.map
@@ -1,285 +0,0 @@
1
- /**
2
- * Anthropic OAuth flow (Claude Pro/Max)
3
- *
4
- * NOTE: This module uses Node.js http.createServer for the OAuth callback server.
5
- * It is only intended for CLI use, not browser environments.
6
- */
7
- import { OAuthTokenEndpointError } from "../../utils/oauth-refresh-error.js";
8
- import { getProviderEnvValue } from "../../utils/provider-env.js";
9
- import { PREFERRED_CALLBACK_PORT, startCallbackListener, } from "./anthropic-callback-listener.js";
10
- import { parseAuthorizationInput } from "./authorization-input.js";
11
- import { formatErrorDetails } from "./error-details.js";
12
- import { generatePKCE } from "./pkce.js";
13
- export function __setAnthropicOAuthNodeApisForTests(apis) {
14
- nodeApis = apis;
15
- nodeApisPromise = null;
16
- }
17
- let nodeApis = null;
18
- let nodeApisPromise = null;
19
- const decode = (s) => atob(s);
20
- const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
21
- const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
22
- const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
23
- const CALLBACK_HOST = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
24
- /**
25
- * A login nobody finishes must not keep its listener (and its manual prompt)
26
- * alive forever: the stale listener would answer a later login's browser
27
- * redirect on this machine with "State mismatch".
28
- */
29
- const LOGIN_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
30
- /** Anthropic page that shows the authorization code to copy (browser on another machine). */
31
- const COPY_CODE_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
32
- const ANTHROPIC_BROWSER_LOGIN_METHOD = "browser";
33
- const ANTHROPIC_COPY_CODE_LOGIN_METHOD = "copy_code";
34
- const SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
35
- async function getNodeApis() {
36
- if (nodeApis)
37
- return nodeApis;
38
- if (!nodeApisPromise) {
39
- if (typeof process === "undefined" || (!process.versions?.node && !process.versions?.bun)) {
40
- throw new Error("Anthropic OAuth is only available in Node.js environments");
41
- }
42
- nodeApisPromise = import("node:http").then((httpModule) => ({
43
- createServer: httpModule.createServer,
44
- }));
45
- }
46
- nodeApis = await nodeApisPromise;
47
- return nodeApis;
48
- }
49
- async function postJson(url, body, signal) {
50
- const response = await fetch(url, {
51
- method: "POST",
52
- headers: {
53
- "Content-Type": "application/json",
54
- Accept: "application/json",
55
- },
56
- body: JSON.stringify(body),
57
- signal: AbortSignal.any([signal, AbortSignal.timeout(30_000)]),
58
- });
59
- const responseBody = await response.text();
60
- if (!response.ok) {
61
- throw new OAuthTokenEndpointError(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`, response.status);
62
- }
63
- return responseBody;
64
- }
65
- async function exchangeAuthorizationCode(code, state, verifier, redirectUri, signal) {
66
- let responseBody;
67
- try {
68
- responseBody = await postJson(TOKEN_URL, {
69
- grant_type: "authorization_code",
70
- client_id: CLIENT_ID,
71
- code,
72
- state,
73
- redirect_uri: redirectUri,
74
- code_verifier: verifier,
75
- }, signal);
76
- }
77
- catch (error) {
78
- throw new Error(`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`, { cause: error });
79
- }
80
- let tokenData;
81
- try {
82
- tokenData = JSON.parse(responseBody);
83
- }
84
- catch (error) {
85
- throw new Error(`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`);
86
- }
87
- return {
88
- type: "oauth",
89
- refresh: tokenData.refresh_token,
90
- access: tokenData.access_token,
91
- expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
92
- };
93
- }
94
- async function loginAnthropic(interaction) {
95
- const { verifier, challenge } = await generatePKCE();
96
- const listener = await startCallbackListener(await getNodeApis(), verifier, CALLBACK_HOST);
97
- const manualAbort = new AbortController();
98
- let timedOut = false;
99
- // Cancelling the login must release BOTH waits: the callback listener and the
100
- // manual prompt. In manual-only mode (no callback port could be bound) the
101
- // prompt is the only thing keeping the login alive, so leaving it open would
102
- // hang cleanup.
103
- const releaseWaits = () => {
104
- listener.cancelWait();
105
- manualAbort.abort();
106
- };
107
- interaction.signal.addEventListener("abort", releaseWaits, { once: true });
108
- if (interaction.signal.aborted)
109
- releaseWaits();
110
- const idleTimer = setTimeout(() => {
111
- timedOut = true;
112
- releaseWaits();
113
- }, LOGIN_IDLE_TIMEOUT_MS);
114
- idleTimer.unref?.();
115
- let code;
116
- let state;
117
- let manualInput;
118
- let manualError;
119
- try {
120
- const authParams = new URLSearchParams({
121
- code: "true",
122
- client_id: CLIENT_ID,
123
- response_type: "code",
124
- redirect_uri: listener.redirectUri,
125
- scope: SCOPES,
126
- code_challenge: challenge,
127
- code_challenge_method: "S256",
128
- state: verifier,
129
- });
130
- interaction.notify({
131
- type: "auth_url",
132
- url: `${AUTHORIZE_URL}?${authParams.toString()}`,
133
- instructions: listener.callbackUnavailable
134
- ? `No local OAuth callback port could be opened (port ${PREFERRED_CALLBACK_PORT} and an ephemeral port both failed: ${listener.callbackUnavailable.code}). Complete login in your browser, then copy the final redirect URL from the address bar and paste it here.`
135
- : "Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.",
136
- });
137
- const manualPromise = interaction
138
- .prompt({
139
- type: "manual_code",
140
- message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
141
- placeholder: listener.redirectUri,
142
- signal: manualAbort.signal,
143
- })
144
- .then((input) => {
145
- manualInput = input;
146
- listener.cancelWait();
147
- })
148
- .catch((error) => {
149
- manualError = error instanceof Error ? error : new Error(String(error));
150
- listener.cancelWait();
151
- });
152
- const result = await listener.waitForCode();
153
- if (timedOut)
154
- throw new Error(loginTimedOutMessage());
155
- if (manualError)
156
- throw manualError;
157
- if (result?.code) {
158
- code = result.code;
159
- state = result.state;
160
- }
161
- else if (manualInput) {
162
- const parsed = parseAuthorizationInput(manualInput);
163
- if (parsed.state && parsed.state !== verifier)
164
- throw new Error("OAuth state mismatch");
165
- code = parsed.code;
166
- state = parsed.state ?? verifier;
167
- }
168
- if (!code) {
169
- await manualPromise;
170
- if (timedOut)
171
- throw new Error(loginTimedOutMessage());
172
- if (manualError)
173
- throw manualError;
174
- if (manualInput) {
175
- const parsed = parseAuthorizationInput(manualInput);
176
- if (parsed.state && parsed.state !== verifier)
177
- throw new Error("OAuth state mismatch");
178
- code = parsed.code;
179
- state = parsed.state ?? verifier;
180
- }
181
- }
182
- if (!code)
183
- throw new Error("Missing authorization code");
184
- if (!state)
185
- throw new Error("Missing OAuth state");
186
- interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
187
- return await exchangeAuthorizationCode(code, state, verifier, listener.redirectUri, interaction.signal);
188
- }
189
- finally {
190
- clearTimeout(idleTimer);
191
- interaction.signal.removeEventListener("abort", releaseWaits);
192
- manualAbort.abort();
193
- await listener.close();
194
- }
195
- }
196
- function loginTimedOutMessage() {
197
- const minutes = Math.round(LOGIN_IDLE_TIMEOUT_MS / 60_000);
198
- return `Anthropic login timed out after ${minutes} minutes without a browser callback or a pasted redirect URL. Run the login again.`;
199
- }
200
- async function loginAnthropicCopyCode(interaction) {
201
- const { verifier, challenge } = await generatePKCE();
202
- const authParams = new URLSearchParams({
203
- code: "true",
204
- client_id: CLIENT_ID,
205
- response_type: "code",
206
- redirect_uri: COPY_CODE_REDIRECT_URI,
207
- scope: SCOPES,
208
- code_challenge: challenge,
209
- code_challenge_method: "S256",
210
- state: verifier,
211
- });
212
- interaction.notify({
213
- type: "auth_url",
214
- url: `${AUTHORIZE_URL}?${authParams.toString()}`,
215
- instructions: "Complete login in your browser, then copy the code Anthropic shows and paste it here.",
216
- });
217
- const input = await interaction.prompt({
218
- type: "manual_code",
219
- message: "Paste the code Anthropic shows after you sign in:",
220
- placeholder: "code#state",
221
- signal: interaction.signal,
222
- });
223
- const parsed = parseAuthorizationInput(input);
224
- if (parsed.state && parsed.state !== verifier)
225
- throw new Error("OAuth state mismatch");
226
- if (!parsed.code)
227
- throw new Error("Missing authorization code");
228
- interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
229
- return await exchangeAuthorizationCode(parsed.code, parsed.state ?? verifier, verifier, COPY_CODE_REDIRECT_URI, interaction.signal);
230
- }
231
- /**
232
- * Refresh Anthropic OAuth token
233
- */
234
- async function refreshAnthropicToken(refreshToken, signal) {
235
- let responseBody;
236
- try {
237
- responseBody = await postJson(TOKEN_URL, {
238
- grant_type: "refresh_token",
239
- client_id: CLIENT_ID,
240
- refresh_token: refreshToken,
241
- }, signal);
242
- }
243
- catch (error) {
244
- throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`, { cause: error });
245
- }
246
- let data;
247
- try {
248
- data = JSON.parse(responseBody);
249
- }
250
- catch (error) {
251
- throw new Error(`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`);
252
- }
253
- return {
254
- type: "oauth",
255
- refresh: data.refresh_token,
256
- access: data.access_token,
257
- expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
258
- };
259
- }
260
- export const anthropicOAuth = {
261
- name: "Anthropic (Claude Pro/Max)",
262
- isSubscription: true,
263
- async login(interaction) {
264
- const method = await interaction.prompt({
265
- type: "select",
266
- message: "Select Anthropic login method:",
267
- options: [
268
- { id: ANTHROPIC_BROWSER_LOGIN_METHOD, label: "Browser login (default)" },
269
- { id: ANTHROPIC_COPY_CODE_LOGIN_METHOD, label: "Copy code login (headless)" },
270
- ],
271
- });
272
- if (method === ANTHROPIC_COPY_CODE_LOGIN_METHOD) {
273
- return loginAnthropicCopyCode(interaction);
274
- }
275
- if (method !== ANTHROPIC_BROWSER_LOGIN_METHOD) {
276
- throw new Error(`Unknown Anthropic login method: ${method}`);
277
- }
278
- return loginAnthropic(interaction);
279
- },
280
- refresh: (credential, signal) => refreshAnthropicToken(credential.refresh, signal),
281
- async toAuth(credential) {
282
- return { apiKey: credential.access };
283
- },
284
- };
285
- //# sourceMappingURL=anthropic.js.map
@@ -1,6 +0,0 @@
1
- /** Parses a pasted redirect URL, `code#state` pair, query string, or bare code. */
2
- export declare function parseAuthorizationInput(input: string): {
3
- code?: string;
4
- state?: string;
5
- };
6
- //# sourceMappingURL=authorization-input.d.ts.map
@@ -1,29 +0,0 @@
1
- /** Parses a pasted redirect URL, `code#state` pair, query string, or bare code. */
2
- export function parseAuthorizationInput(input) {
3
- const value = input.trim();
4
- if (!value)
5
- return {};
6
- try {
7
- const url = new URL(value);
8
- return {
9
- code: url.searchParams.get("code") ?? undefined,
10
- state: url.searchParams.get("state") ?? undefined,
11
- };
12
- }
13
- catch {
14
- // not a URL
15
- }
16
- if (value.includes("#")) {
17
- const [code, state] = value.split("#", 2);
18
- return { code, state };
19
- }
20
- if (value.includes("code=")) {
21
- const params = new URLSearchParams(value);
22
- return {
23
- code: params.get("code") ?? undefined,
24
- state: params.get("state") ?? undefined,
25
- };
26
- }
27
- return { code: value };
28
- }
29
- //# sourceMappingURL=authorization-input.js.map
@@ -1,3 +0,0 @@
1
- /** Flattens an error (with `code`, `errno`, `cause`, and stack) into one diagnostic line. */
2
- export declare function formatErrorDetails(error: unknown): string;
3
- //# sourceMappingURL=error-details.d.ts.map
@@ -1,20 +0,0 @@
1
- /** Flattens an error (with `code`, `errno`, `cause`, and stack) into one diagnostic line. */
2
- export function formatErrorDetails(error) {
3
- if (error instanceof Error) {
4
- const details = [`${error.name}: ${error.message}`];
5
- const errorWithCode = error;
6
- if (errorWithCode.code)
7
- details.push(`code=${errorWithCode.code}`);
8
- if (typeof errorWithCode.errno !== "undefined")
9
- details.push(`errno=${String(errorWithCode.errno)}`);
10
- if (typeof error.cause !== "undefined") {
11
- details.push(`cause=${formatErrorDetails(error.cause)}`);
12
- }
13
- if (error.stack) {
14
- details.push(`stack=${error.stack}`);
15
- }
16
- return details.join("; ");
17
- }
18
- return String(error);
19
- }
20
- //# sourceMappingURL=error-details.js.map
@@ -1,12 +0,0 @@
1
- import { type ClaudeCodeVersionStore } from "./claude-code-version.ts";
2
- /** Same resolution as the other pi-ai stores: the agent directory the host configured, then the default one. */
3
- export declare function resolveClaudeCodeVersionCachePath(env?: NodeJS.ProcessEnv): string;
4
- export declare function createClaudeCodeVersionFileStore(path: string): ClaudeCodeVersionStore;
5
- export interface InstallClaudeCodeVersionFileStoreOptions {
6
- /** Cache file; defaults to `<agent dir>/claude-code-version.json`. */
7
- readonly path?: string;
8
- /** Serve the cache but never fetch (host offline mode); defaults to `PI_OFFLINE`. */
9
- readonly offline?: boolean;
10
- }
11
- export declare function installClaudeCodeVersionFileStore(options?: InstallClaudeCodeVersionFileStoreOptions): void;
12
- //# sourceMappingURL=claude-code-version-cache.d.ts.map
@@ -1,60 +0,0 @@
1
- /**
2
- * Node entry point for the Claude Code version cache.
3
- *
4
- * `claude-code-version.ts` is browser-safe and keeps no state on disk. Hosts with a filesystem
5
- * call `installClaudeCodeVersionFileStore()` once at startup so the resolved version survives
6
- * process restarts and one lookup per six hours serves every session on the machine.
7
- */
8
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
9
- import { dirname } from "node:path";
10
- import { installClaudeCodeVersionStore, } from "./claude-code-version.js";
11
- const CACHE_FILE = "claude-code-version.json";
12
- /** Same resolution as the other pi-ai stores: the agent directory the host configured, then the default one. */
13
- export function resolveClaudeCodeVersionCachePath(env = process.env) {
14
- const agentDir = env.SENPI_CODING_AGENT_DIR ?? env.CODING_AGENT_DIR ?? `${(env.HOME ?? ".").replace(/\/$/, "")}/.senpi/agent`;
15
- return `${agentDir.replace(/\/$/, "")}/${CACHE_FILE}`;
16
- }
17
- function isCachedVersion(value) {
18
- return (typeof value === "object" &&
19
- value !== null &&
20
- typeof value.version === "string" &&
21
- typeof value.checkedAt === "number");
22
- }
23
- export function createClaudeCodeVersionFileStore(path) {
24
- let savingEnabled = true;
25
- return {
26
- load: () => {
27
- let parsed;
28
- try {
29
- parsed = JSON.parse(readFileSync(path, "utf8"));
30
- }
31
- catch {
32
- // No file on first run, and a truncated or hand-edited one is the same situation
33
- // for a cache: the bundled floor is the documented fallback.
34
- return undefined;
35
- }
36
- return isCachedVersion(parsed) ? parsed : undefined;
37
- },
38
- save: (record) => {
39
- if (!savingEnabled)
40
- return;
41
- try {
42
- mkdirSync(dirname(path), { recursive: true });
43
- const temporaryPath = `${path}.${process.pid}.tmp`;
44
- writeFileSync(temporaryPath, `${JSON.stringify(record)}\n`);
45
- renameSync(temporaryPath, path);
46
- }
47
- catch {
48
- // A read-only agent directory must not fail the live request, and one failure
49
- // disables saving so a broken path cannot cost a write per refresh.
50
- savingEnabled = false;
51
- }
52
- },
53
- };
54
- }
55
- export function installClaudeCodeVersionFileStore(options = {}) {
56
- const env = process.env;
57
- const offline = options.offline ?? (env.PI_OFFLINE !== undefined && env.PI_OFFLINE !== "" && env.PI_OFFLINE !== "0");
58
- installClaudeCodeVersionStore(createClaudeCodeVersionFileStore(options.path ?? resolveClaudeCodeVersionCachePath(env)), { offline });
59
- }
60
- //# sourceMappingURL=claude-code-version-cache.js.map
@@ -1,78 +0,0 @@
1
- /**
2
- * Claude Code version the Anthropic OAuth `claude-cli/<version>` fingerprint advertises.
3
- *
4
- * Anthropic gates new models behind a minimum Claude Code version and answers an older
5
- * fingerprint with HTTP 400 `claude_code_version_too_old`. A build-time constant goes stale
6
- * between releases, so the fingerprint is the higher of the bundled floor and the latest
7
- * published Claude Code release, resolved in the background and cached by the host through
8
- * `ClaudeCodeVersionStore`. A request never waits on the lookup: it signs with whatever is
9
- * known at that instant, and a too-old rejection raises the version at once.
10
- */
11
- import type { ProviderEnv } from "../types.ts";
12
- /** Exact `X.Y.Z` pins the fingerprint and skips the network lookup. */
13
- export declare const CLAUDE_CODE_VERSION_PIN_ENV = "PI_CLAUDE_CODE_VERSION";
14
- /**
15
- * Anthropic's native-installer release channel. `stable` lags the npm package during a staged
16
- * rollout, so `latest` is the channel that matches what a freshly updated Claude Code reports.
17
- */
18
- export declare const CLAUDE_RELEASE_CHANNEL_URL = "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest";
19
- export declare const CLAUDE_NPM_DIST_TAG_URL = "https://registry.npmjs.org/@anthropic-ai/claude-code/latest";
20
- export interface CachedClaudeCodeVersion {
21
- readonly version: string;
22
- readonly checkedAt: number;
23
- }
24
- export interface ClaudeCodeVersionStore {
25
- load(): CachedClaudeCodeVersion | undefined;
26
- save(record: CachedClaudeCodeVersion): void;
27
- }
28
- export interface ClaudeCodeVersionResolverOptions {
29
- readonly floor: string;
30
- readonly pinned?: string | undefined;
31
- /** `null` disables caching and network resolution: the resolver answers the floor or a raised version. */
32
- readonly store: ClaudeCodeVersionStore | null;
33
- /** Serve the cache but never fetch (host offline mode). */
34
- readonly offline?: boolean;
35
- readonly fetch?: typeof fetch;
36
- readonly now?: () => number;
37
- readonly refreshIntervalMs?: number;
38
- }
39
- export interface ClaudeCodeVersionResolver {
40
- /** Current fingerprint; schedules one background refresh when the cache is stale. */
41
- get(): string;
42
- /** Refresh now (deduplicated); resolves with the fingerprint afterwards. */
43
- refresh(): Promise<string>;
44
- /** Adopt `version` at once when it is newer than the current fingerprint; returns the fingerprint. */
45
- raise(version: string): string;
46
- }
47
- export declare function compareClaudeCodeVersions(a: string, b: string): number;
48
- /**
49
- * Latest published Claude Code version: the higher of the native-installer `latest` channel and
50
- * the npm dist-tag, so one lagging or unavailable source cannot hold the fingerprint back.
51
- */
52
- export declare function fetchLatestClaudeCodeVersion(fetchImpl?: typeof fetch): Promise<string | undefined>;
53
- export declare function requiredClaudeCodeVersionFromError(message: string): string | undefined;
54
- export declare function isClaudeCodeVersionTooOldError(error: unknown): error is Error & {
55
- status: 400;
56
- };
57
- export declare function createClaudeCodeVersionResolver(options: ClaudeCodeVersionResolverOptions): ClaudeCodeVersionResolver;
58
- /**
59
- * Hosts with a filesystem install their cache here once at startup (see
60
- * `utils/claude-code-version-cache.ts`); until then, and in browsers, the resolver answers
61
- * the floor without touching the network.
62
- */
63
- export declare function installClaudeCodeVersionStore(store: ClaudeCodeVersionStore | null, options?: {
64
- readonly offline?: boolean;
65
- }): void;
66
- /**
67
- * The version the next OAuth request signs with. `floor` is the bundled Claude Code version the
68
- * caller declares (the `claudeCodeVersion` constant in `api/anthropic-messages.ts`).
69
- */
70
- export declare function getClaudeCodeVersion(floor: string, env?: ProviderEnv): string;
71
- /**
72
- * After a `claude_code_version_too_old` rejection: adopt the version the error names, or the
73
- * latest published one, and report the fingerprint to retry with; `undefined` when nothing
74
- * newer is available (pinned, or already at the required version).
75
- */
76
- export declare function recoverClaudeCodeVersion(error: Error, floor: string, advertised: string, env?: ProviderEnv): Promise<string | undefined>;
77
- export declare function claudeCodeVersionTooOldHint(advertised: string): string;
78
- //# sourceMappingURL=claude-code-version.d.ts.map