@modelprofile.com/authswitch 5.1.0 → 6.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.
@@ -1,11 +1,14 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { authSwitchHome } from './classes.credentialstore.js';
3
+ import { assertAuthSwitchMutation, type TAuthSwitchMutation } from './mutation.js';
3
4
  import type { IAuthHarness, IHarnessOutcome } from './interfaces.harness.js';
4
5
 
5
- export type TAuthSwitchMutation = { harnessId: string } & (
6
- | { action: 'save'; accountId?: string; keepActive: boolean }
7
- | { action: 'switch' | 'remove'; accountId: string }
8
- );
6
+ /**
7
+ * The mutation command and its rules live in a leaf module so that a browser host can import them
8
+ * through `@modelprofile.com/authswitch/mutation`; they stay exported here for every Node caller.
9
+ */
10
+ export { assertAuthSwitchMutation, authSwitchMutationReplacesLogin, type TAuthSwitchMutation } from './mutation.js';
11
+
9
12
  export interface IAuthSwitchCoordinationRequest {
10
13
  protocolVersion: 1;
11
14
  /** Identifies credential locations; a host must refuse a different environment. */
@@ -38,24 +41,6 @@ export const authSwitchEnvironmentId = (env: NodeJS.ProcessEnv = process.env): s
38
41
  openCodeOverride: env.OPENCODE_AUTH_CONTENT !== undefined,
39
42
  })).digest('hex');
40
43
 
41
- /** Validate the secret-free command boundary, including callers that do not use TypeScript. */
42
- export const assertAuthSwitchMutation: (value: unknown) => asserts value is TAuthSwitchMutation = (value) => {
43
- if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid account operation.');
44
- const item = value as Record<string, unknown>;
45
- const keys = item.action === 'save' ? ['harnessId', 'action', 'accountId', 'keepActive'] : ['harnessId', 'action', 'accountId'];
46
- if (Object.keys(item).some(key => !keys.includes(key))
47
- || typeof item.harnessId !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.harnessId)
48
- || !['save', 'switch', 'remove'].includes(String(item.action))
49
- || (item.accountId !== undefined && (typeof item.accountId !== 'string' || !item.accountId.length || item.accountId.length > 512))
50
- || (item.action === 'save' ? typeof item.keepActive !== 'boolean' : typeof item.accountId !== 'string')) {
51
- throw new Error('Invalid account operation.');
52
- }
53
- };
54
-
55
- /** Whether a mutation replaces the harness's active login, and therefore concerns running instances. */
56
- export const authSwitchMutationReplacesLogin = (mutationArg: TAuthSwitchMutation): boolean =>
57
- mutationArg.action === 'switch' || (mutationArg.action === 'save' && !mutationArg.keepActive);
58
-
59
44
  export const runAuthSwitchMutation = async (harness: IAuthHarness, mutation: TAuthSwitchMutation): Promise<IHarnessOutcome> => {
60
45
  assertAuthSwitchMutation(mutation);
61
46
  if (harness.id !== mutation.harnessId) throw new Error('Account operation targets a different harness.');
package/ts/claudehttp.ts CHANGED
@@ -13,6 +13,28 @@ export class ClaudeLoginRejectedError extends ClaudeRequestError {
13
13
  constructor() { super(CLAUDE_LOGIN_REJECTED); }
14
14
  }
15
15
 
16
+ /**
17
+ * Where Claude Code 2.1.273 sends an account the service put on hold (`T5e`, bundle offset @190070402). A hold is
18
+ * not a dead login: the service refuses the grant while it lasts and accepts it again once it is lifted.
19
+ */
20
+ export const CLAUDE_ACCOUNT_ON_HOLD_URL = 'https://claude.ai/restricted';
21
+
22
+ /** The service put this account on hold. The login itself is intact, so it is never remembered as refused. */
23
+ export class ClaudeAccountOnHoldError extends ClaudeRequestError {
24
+ constructor() { super(`This Claude account is on hold and cannot sign in. View details or appeal: ${CLAUDE_ACCOUNT_ON_HOLD_URL}`); }
25
+ }
26
+
27
+ /**
28
+ * The OAuth error code a response names, read as Claude Code 2.1.273 reads it (`xc`, @191165290): the `error` field
29
+ * as a string, or the `type` of the object some Anthropic endpoints send it as.
30
+ */
31
+ export const oauthErrorCode = (bodyArg: Record<string, unknown> | null): string | undefined => {
32
+ const error = bodyArg?.error;
33
+ if (typeof error === 'string') return error;
34
+ const type = typeof error === 'object' && error !== null ? (error as Record<string, unknown>).type : undefined;
35
+ return typeof type === 'string' ? type : undefined;
36
+ };
37
+
16
38
  /** The service refused a request for too many requests. That says nothing about the account's usage or login. */
17
39
  export class ClaudeRateLimitError extends ClaudeRequestError {
18
40
  constructor(subjectArg: string, public readonly retryAt: string | null) {
package/ts/mutation.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The account mutation command and the rules that read it, with no dependency of any kind.
3
+ *
4
+ * A host decides what a mutation means to the instances it runs long before it can touch a
5
+ * credential, and a browser is such a host. This module therefore imports nothing -- no Node
6
+ * built-in, no package, not even another module of this package -- so that it can be bundled
7
+ * for a browser through the `@modelprofile.com/authswitch/mutation` entry point. Everything
8
+ * that reads a credential stays behind the main entry point, which is Node-only.
9
+ */
10
+
11
+ export type TAuthSwitchMutation = { harnessId: string } & (
12
+ | { action: 'save'; accountId?: string; keepActive: boolean }
13
+ | { action: 'switch' | 'remove'; accountId: string }
14
+ );
15
+
16
+ /** Validate the secret-free command boundary, including callers that do not use TypeScript. */
17
+ export const assertAuthSwitchMutation: (value: unknown) => asserts value is TAuthSwitchMutation = (value) => {
18
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid account operation.');
19
+ const item = value as Record<string, unknown>;
20
+ const keys = item.action === 'save' ? ['harnessId', 'action', 'accountId', 'keepActive'] : ['harnessId', 'action', 'accountId'];
21
+ if (Object.keys(item).some(key => !keys.includes(key))
22
+ || typeof item.harnessId !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.harnessId)
23
+ || !['save', 'switch', 'remove'].includes(String(item.action))
24
+ || (item.accountId !== undefined && (typeof item.accountId !== 'string' || !item.accountId.length || item.accountId.length > 512))
25
+ || (item.action === 'save' ? typeof item.keepActive !== 'boolean' : typeof item.accountId !== 'string')) {
26
+ throw new Error('Invalid account operation.');
27
+ }
28
+ };
29
+
30
+ /** Whether a mutation replaces the harness's active login, and therefore concerns running instances. */
31
+ export const authSwitchMutationReplacesLogin = (mutationArg: TAuthSwitchMutation): boolean =>
32
+ mutationArg.action === 'switch' || (mutationArg.action === 'save' && !mutationArg.keepActive);