axauth 2.0.0 → 3.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.
Files changed (50) hide show
  1. package/README.md +10 -4
  2. package/dist/auth/adapter.d.ts +60 -20
  3. package/dist/auth/agents/claude-install.d.ts +11 -0
  4. package/dist/auth/agents/claude-install.js +80 -0
  5. package/dist/auth/agents/claude-storage.d.ts +13 -5
  6. package/dist/auth/agents/claude-storage.js +11 -16
  7. package/dist/auth/agents/claude.js +14 -89
  8. package/dist/auth/agents/codex-auth-check.d.ts +6 -5
  9. package/dist/auth/agents/codex-auth-check.js +34 -20
  10. package/dist/auth/agents/codex-install.js +1 -2
  11. package/dist/auth/agents/codex-storage.d.ts +0 -1
  12. package/dist/auth/agents/codex-storage.js +4 -5
  13. package/dist/auth/agents/codex.js +4 -4
  14. package/dist/auth/agents/copilot-install.js +2 -3
  15. package/dist/auth/agents/copilot.js +11 -6
  16. package/dist/auth/agents/gemini-install.d.ts +1 -1
  17. package/dist/auth/agents/gemini-install.js +3 -3
  18. package/dist/auth/agents/gemini.js +19 -17
  19. package/dist/auth/agents/opencode-credentials.d.ts +25 -37
  20. package/dist/auth/agents/opencode-credentials.js +48 -122
  21. package/dist/auth/agents/opencode-remove-provider.d.ts +25 -0
  22. package/dist/auth/agents/opencode-remove-provider.js +86 -0
  23. package/dist/auth/agents/opencode-storage.js +18 -22
  24. package/dist/auth/agents/opencode.d.ts +1 -1
  25. package/dist/auth/agents/opencode.js +59 -41
  26. package/dist/auth/build-refreshed-credentials.d.ts +6 -4
  27. package/dist/auth/build-refreshed-credentials.js +34 -19
  28. package/dist/auth/extract-creds-from-directory.d.ts +7 -3
  29. package/dist/auth/extract-creds-from-directory.js +4 -1
  30. package/dist/auth/is-token-expired.d.ts +52 -2
  31. package/dist/auth/is-token-expired.js +71 -5
  32. package/dist/auth/refresh-credentials.d.ts +38 -12
  33. package/dist/auth/refresh-credentials.js +49 -18
  34. package/dist/auth/registry.d.ts +18 -15
  35. package/dist/auth/registry.js +35 -18
  36. package/dist/auth/resolve-refresh-credentials.d.ts +11 -8
  37. package/dist/auth/resolve-refresh-credentials.js +12 -48
  38. package/dist/auth/types.d.ts +3 -35
  39. package/dist/auth/types.js +2 -47
  40. package/dist/auth/wait-for-refreshed-credentials.d.ts +5 -1
  41. package/dist/auth/wait-for-refreshed-credentials.js +7 -4
  42. package/dist/commands/auth-export.js +24 -46
  43. package/dist/commands/auth.js +13 -3
  44. package/dist/commands/copy-to-clipboard.d.ts +6 -0
  45. package/dist/commands/copy-to-clipboard.js +48 -0
  46. package/dist/commands/vault.js +16 -15
  47. package/dist/index.d.ts +3 -3
  48. package/dist/index.js +4 -4
  49. package/dist/vault/vault-client.js +21 -2
  50. package/package.json +11 -11
@@ -11,4 +11,4 @@ import type { AuthAdapter } from "../adapter.js";
11
11
  /** OpenCode authentication adapter */
12
12
  declare const opencodeAdapter: AuthAdapter;
13
13
  export { opencodeAdapter };
14
- export { extractProviderCredentials, removeProviderCredentials, splitToPerProviderCredentials, } from "./opencode-credentials.js";
14
+ export { getAllStoredCredentials, getStoredCredentialsForProvider, removeProviderCredentials, } from "./opencode-credentials.js";
@@ -8,9 +8,8 @@
8
8
  * - Data (auth): ~/.local/share/opencode (XDG_DATA_HOME/opencode)
9
9
  */
10
10
  import path from "node:path";
11
- import { extractCredsFromDirectory } from "../extract-creds-from-directory.js";
12
11
  import { extractTokenFromEntry, OpenCodeAuth } from "./opencode-schema.js";
13
- import { AGENT_ID, CREDS_FILE_NAME, getDefaultAuthFilePath, readAuthFile, } from "./opencode-credentials.js";
12
+ import { AGENT_ID, CREDS_FILE_NAME, getDefaultAuthFilePath, mapToCredentialType, readAuthFile, } from "./opencode-credentials.js";
14
13
  import { installCredentials, removeCredentials } from "./opencode-storage.js";
15
14
  /** OpenCode authentication adapter */
16
15
  const opencodeAdapter = {
@@ -54,59 +53,78 @@ const opencodeAdapter = {
54
53
  details: { providers: validProviders.map((p) => p.name) },
55
54
  };
56
55
  },
57
- extractRawCredentials() {
56
+ findStoredCredentials(options) {
57
+ if (!options?.provider) {
58
+ throw new Error("OpenCode requires provider parameter. Use checkAuth() to list available providers.");
59
+ }
60
+ const normalizedProvider = options.provider.trim();
61
+ if (normalizedProvider.length === 0) {
62
+ throw new Error("Provider cannot be empty");
63
+ }
58
64
  const authResult = readAuthFile(getDefaultAuthFilePath());
59
65
  // Treat errors same as missing file for extraction (return undefined)
60
- if (!authResult.ok ||
61
- !authResult.data ||
62
- Object.keys(authResult.data).length === 0) {
66
+ if (!authResult.ok || !authResult.data) {
67
+ return undefined;
68
+ }
69
+ const entry = authResult.data[normalizedProvider];
70
+ if (!entry) {
71
+ return undefined;
72
+ }
73
+ const parsed = OpenCodeAuth.safeParse(entry);
74
+ if (!parsed.success) {
63
75
  return undefined;
64
76
  }
65
77
  return {
66
- agent: AGENT_ID,
67
- type: "oauth-credentials",
68
- data: authResult.data,
78
+ credentials: {
79
+ agent: AGENT_ID,
80
+ type: mapToCredentialType(parsed.data),
81
+ provider: normalizedProvider,
82
+ data: entry,
83
+ },
84
+ source: "file",
69
85
  };
70
86
  },
71
- extractRawCredentialsFromDirectory(options) {
87
+ loadCredentialsFromDirectory(options) {
72
88
  // OpenCode stores credentials in dataDir only - configDir is for settings
73
- // If dataDir not provided, return undefined (use extractRawCredentials() for default)
89
+ // If dataDir not provided, return undefined; if provider not provided, throw
74
90
  if (!options.dataDir)
75
91
  return undefined;
76
- // Only return if there's at least one provider
77
- return extractCredsFromDirectory(AGENT_ID, options.dataDir, CREDS_FILE_NAME, (data) => (Object.keys(data).length > 0 ? data : undefined));
78
- },
79
- installCredentials,
80
- removeCredentials,
81
- getAccessToken(creds, options) {
82
- const data = creds.data;
83
- // Per-provider credential: data is the provider's entry directly
84
- if (creds.provider) {
85
- const parsed = OpenCodeAuth.safeParse(data);
86
- if (!parsed.success)
87
- return undefined;
88
- return extractTokenFromEntry(parsed.data);
92
+ const authResult = readAuthFile(path.join(options.dataDir, CREDS_FILE_NAME));
93
+ // Treat errors same as missing file for extraction (return undefined)
94
+ if (!authResult.ok ||
95
+ !authResult.data ||
96
+ Object.keys(authResult.data).length === 0) {
97
+ return undefined;
89
98
  }
90
- // Bundled credential: data contains multiple providers
91
- // If provider specified in options, get that provider's token
92
- if (options?.provider) {
93
- const normalizedProvider = options.provider.trim();
94
- const entry = data[normalizedProvider];
95
- const parsed = OpenCodeAuth.safeParse(entry);
96
- if (!parsed.success)
99
+ // If provider filter is specified, return only that provider's credentials
100
+ if (options.provider) {
101
+ const targetProvider = options.provider.trim();
102
+ if (targetProvider.length === 0)
103
+ return undefined;
104
+ const entry = authResult.data[targetProvider];
105
+ if (!entry)
97
106
  return undefined;
98
- return extractTokenFromEntry(parsed.data);
99
- }
100
- // Otherwise return first available token
101
- for (const entry of Object.values(data)) {
102
107
  const parsed = OpenCodeAuth.safeParse(entry);
103
108
  if (!parsed.success)
104
- continue;
105
- const token = extractTokenFromEntry(parsed.data);
106
- if (token)
107
- return token;
109
+ return undefined;
110
+ return {
111
+ agent: AGENT_ID,
112
+ type: mapToCredentialType(parsed.data),
113
+ provider: targetProvider,
114
+ data: entry,
115
+ };
108
116
  }
109
- return undefined;
117
+ // No provider filter: throw to enforce explicit provider requirement
118
+ throw new Error("OpenCode requires provider parameter for loadCredentialsFromDirectory. Use findStoredCredentials() with provider option.");
119
+ },
120
+ installCredentials,
121
+ removeCredentials,
122
+ getAccessToken(creds) {
123
+ // OpenCode credentials are always per-provider: data is the provider's entry
124
+ const parsed = OpenCodeAuth.safeParse(creds.data);
125
+ if (!parsed.success)
126
+ return undefined;
127
+ return extractTokenFromEntry(parsed.data);
110
128
  },
111
129
  credentialsToEnvironment() {
112
130
  // OpenCode requires auth.json file, no env var support
@@ -117,4 +135,4 @@ const opencodeAdapter = {
117
135
  },
118
136
  };
119
137
  export { opencodeAdapter };
120
- export { extractProviderCredentials, removeProviderCredentials, splitToPerProviderCredentials, } from "./opencode-credentials.js";
138
+ export { getAllStoredCredentials, getStoredCredentialsForProvider, removeProviderCredentials, } from "./opencode-credentials.js";
@@ -1,12 +1,14 @@
1
1
  /**
2
- * Build refreshed credentials with source and provider context preserved.
2
+ * Build refreshed credentials with provider context preserved.
3
3
  */
4
4
  import type { Credentials } from "./types.js";
5
5
  /**
6
- * Build refreshed credentials, preserving _source marker and handling
7
- * per-provider vs bundled credential formats.
6
+ * Build refreshed credentials, preserving provider context.
8
7
  *
9
- * @param originalCreds - Original credentials with _source marker
8
+ * For OpenCode (per-provider), extracts the specific provider's refreshed data.
9
+ * For other agents, uses the refreshed data directly.
10
+ *
11
+ * @param originalCreds - Original credentials
10
12
  * @param refreshedCreds - Credentials returned from refresh operation
11
13
  * @returns Built credentials or error message
12
14
  */
@@ -1,20 +1,32 @@
1
1
  /**
2
- * Build refreshed credentials with source and provider context preserved.
2
+ * Build refreshed credentials with provider context preserved.
3
3
  */
4
4
  /**
5
- * Build refreshed credentials, preserving _source marker and handling
6
- * per-provider vs bundled credential formats.
5
+ * Build refreshed credentials, preserving provider context.
7
6
  *
8
- * @param originalCreds - Original credentials with _source marker
7
+ * For OpenCode (per-provider), extracts the specific provider's refreshed data.
8
+ * For other agents, uses the refreshed data directly.
9
+ *
10
+ * @param originalCreds - Original credentials
9
11
  * @param refreshedCreds - Credentials returned from refresh operation
10
12
  * @returns Built credentials or error message
11
13
  */
12
14
  function buildRefreshedCredentials(originalCreds, refreshedCreds) {
13
- // Per-provider refresh: extract just the refreshed provider's data
14
- if (originalCreds.provider) {
15
+ // OpenCode per-provider refresh: verify provider matches and use data directly
16
+ if (originalCreds.agent === "opencode" && originalCreds.provider) {
15
17
  const normalizedProvider = originalCreds.provider.trim();
16
- const providerData = refreshedCreds.data[normalizedProvider];
17
- if (!providerData || typeof providerData !== "object") {
18
+ // refreshedCreds from loadCredentialsFromDirectory is now per-provider format:
19
+ // - refreshedCreds.provider = the provider name
20
+ // - refreshedCreds.data = the provider's auth entry directly
21
+ if (refreshedCreds.agent !== "opencode") {
22
+ return {
23
+ ok: false,
24
+ error: `Provider '${normalizedProvider}' not found in refreshed credentials`,
25
+ };
26
+ }
27
+ // TypeScript narrows to OpenCodeCredentials which has provider as required
28
+ // (z.string().trim().min(1) in axshared) - NOT optional despite Credentials union
29
+ if (refreshedCreds.provider.trim() !== normalizedProvider) {
18
30
  return {
19
31
  ok: false,
20
32
  error: `Provider '${normalizedProvider}' not found in refreshed credentials`,
@@ -24,24 +36,27 @@ function buildRefreshedCredentials(originalCreds, refreshedCreds) {
24
36
  ok: true,
25
37
  credentials: {
26
38
  agent: originalCreds.agent,
27
- type: originalCreds.type,
39
+ type: refreshedCreds.type,
28
40
  provider: normalizedProvider,
29
- data: {
30
- ...providerData,
31
- _source: originalCreds.data._source,
32
- },
41
+ data: refreshedCreds.data,
33
42
  },
34
43
  };
35
44
  }
36
- // Bundled refresh: use all credentials as-is
45
+ // Standard agent refresh: use refreshed data
46
+ // Both originalCreds and refreshedCreds are standard agents here (not OpenCode)
47
+ if (originalCreds.agent === "opencode") {
48
+ // TypeScript narrowing: unreachable, but satisfies type checker
49
+ return {
50
+ ok: false,
51
+ error: "Unexpected OpenCode credential without provider",
52
+ };
53
+ }
37
54
  return {
38
55
  ok: true,
39
56
  credentials: {
40
- ...refreshedCreds,
41
- data: {
42
- ...refreshedCreds.data,
43
- _source: originalCreds.data._source,
44
- },
57
+ agent: originalCreds.agent,
58
+ type: refreshedCreds.type,
59
+ data: refreshedCreds.data,
45
60
  },
46
61
  };
47
62
  }
@@ -1,16 +1,20 @@
1
1
  /**
2
2
  * Common utility for extracting credentials from a directory.
3
3
  */
4
- import type { AgentCli } from "axshared";
5
4
  import type { Credentials } from "./types.js";
5
+ /** Standard agents (all except OpenCode) */
6
+ type StandardAgentCli = "claude" | "codex" | "gemini" | "copilot";
6
7
  /**
7
8
  * Extract credentials from a JSON file in a directory.
8
9
  *
9
- * @param agentId - The agent ID
10
+ * Only for standard agents (claude, codex, gemini, copilot).
11
+ * OpenCode has its own extraction logic due to different credential structure.
12
+ *
13
+ * @param agentId - The agent ID (standard agents only)
10
14
  * @param directory - Directory to read from
11
15
  * @param fileName - Name of the credentials file
12
16
  * @param transform - Optional transform to apply to file contents
13
17
  * @returns Credentials or undefined if not found
14
18
  */
15
- declare function extractCredsFromDirectory(agentId: AgentCli, directory: string, fileName: string, transform?: (data: Record<string, unknown>) => Record<string, unknown> | undefined): Credentials | undefined;
19
+ declare function extractCredsFromDirectory(agentId: StandardAgentCli, directory: string, fileName: string, transform?: (data: Record<string, unknown>) => Record<string, unknown> | undefined): Credentials | undefined;
16
20
  export { extractCredsFromDirectory };
@@ -6,7 +6,10 @@ import path from "node:path";
6
6
  /**
7
7
  * Extract credentials from a JSON file in a directory.
8
8
  *
9
- * @param agentId - The agent ID
9
+ * Only for standard agents (claude, codex, gemini, copilot).
10
+ * OpenCode has its own extraction logic due to different credential structure.
11
+ *
12
+ * @param agentId - The agent ID (standard agents only)
10
13
  * @param directory - Directory to read from
11
14
  * @param fileName - Name of the credentials file
12
15
  * @param transform - Optional transform to apply to file contents
@@ -1,5 +1,11 @@
1
1
  /**
2
- * Token and credential expiry check utilities.
2
+ * Token and credential expiry/refresh utilities.
3
+ *
4
+ * Provides field detection for common OAuth credential formats:
5
+ * - Google OAuth: expiry_date (ms), refresh_token
6
+ * - Generic OAuth: expires_at (s or ms), refresh_token
7
+ * - OpenCode: expires (ms), refresh
8
+ * - Claude: expiresAt (ms), refreshToken (camelCase)
3
9
  */
4
10
  /**
5
11
  * Check if a JWT token is expired.
@@ -23,4 +29,48 @@ declare function isTokenExpired(token: string, bufferSeconds?: number): boolean
23
29
  * @returns true if expired, false if valid, undefined if no expiry field found
24
30
  */
25
31
  declare function isCredentialExpired(data: Record<string, unknown>, bufferSeconds?: number): boolean | undefined;
26
- export { isCredentialExpired, isTokenExpired };
32
+ /**
33
+ * Extract expiry timestamp in milliseconds from credential data.
34
+ *
35
+ * Checks common expiry field names in order of preference:
36
+ * - `expiry_date`: Google OAuth (milliseconds)
37
+ * - `expires_at`: Generic OAuth (seconds or milliseconds, auto-detected)
38
+ * - `expiresAt`: Claude (seconds or milliseconds, auto-detected)
39
+ * - `expires`: OpenCode (seconds or milliseconds, auto-detected)
40
+ */
41
+ declare function extractExpiryTimestamp(data: Record<string, unknown>): number | undefined;
42
+ /**
43
+ * Extract expiry date from credential data.
44
+ *
45
+ * Convenience wrapper around {@link extractExpiryTimestamp} that returns
46
+ * a Date object instead of milliseconds.
47
+ *
48
+ * @param data - The credential data object
49
+ * @returns Date if expiry field found, undefined otherwise
50
+ */
51
+ declare function extractExpiryDate(data: Record<string, unknown>): Date | undefined;
52
+ /**
53
+ * Credential data containing a refresh token.
54
+ *
55
+ * Union of all supported refresh token field naming conventions.
56
+ */
57
+ type RefreshableCredentials = (Record<string, unknown> & {
58
+ refresh_token: string;
59
+ }) | (Record<string, unknown> & {
60
+ refreshToken: string;
61
+ }) | (Record<string, unknown> & {
62
+ refresh: string;
63
+ });
64
+ /**
65
+ * Check if credential data contains a refresh token.
66
+ *
67
+ * Supports multiple field naming conventions:
68
+ * - `refresh_token`: Standard OAuth / Google / Generic
69
+ * - `refreshToken`: Claude (camelCase)
70
+ * - `refresh`: OpenCode
71
+ *
72
+ * @param data - The credential data object (accepts unknown for safety)
73
+ * @returns true if a refresh token field exists with a string value
74
+ */
75
+ declare function isRefreshable(data: unknown): data is RefreshableCredentials;
76
+ export { extractExpiryDate, extractExpiryTimestamp, isCredentialExpired, isRefreshable, isTokenExpired, type RefreshableCredentials, };
@@ -1,5 +1,11 @@
1
1
  /**
2
- * Token and credential expiry check utilities.
2
+ * Token and credential expiry/refresh utilities.
3
+ *
4
+ * Provides field detection for common OAuth credential formats:
5
+ * - Google OAuth: expiry_date (ms), refresh_token
6
+ * - Generic OAuth: expires_at (s or ms), refresh_token
7
+ * - OpenCode: expires (ms), refresh
8
+ * - Claude: expiresAt (ms), refreshToken (camelCase)
3
9
  */
4
10
  /**
5
11
  * Timestamp threshold to distinguish seconds from milliseconds.
@@ -65,7 +71,15 @@ function isCredentialExpired(data, bufferSeconds = 60) {
65
71
  const bufferMs = bufferSeconds * 1000;
66
72
  return nowMs > expiryTimestampMs - bufferMs;
67
73
  }
68
- /** Extract expiry timestamp in milliseconds from credential data. */
74
+ /**
75
+ * Extract expiry timestamp in milliseconds from credential data.
76
+ *
77
+ * Checks common expiry field names in order of preference:
78
+ * - `expiry_date`: Google OAuth (milliseconds)
79
+ * - `expires_at`: Generic OAuth (seconds or milliseconds, auto-detected)
80
+ * - `expiresAt`: Claude (seconds or milliseconds, auto-detected)
81
+ * - `expires`: OpenCode (seconds or milliseconds, auto-detected)
82
+ */
69
83
  function extractExpiryTimestamp(data) {
70
84
  // Google OAuth uses expiry_date (milliseconds)
71
85
  if (typeof data.expiry_date === "number") {
@@ -77,10 +91,62 @@ function extractExpiryTimestamp(data) {
77
91
  ? data.expires_at * 1000
78
92
  : data.expires_at;
79
93
  }
80
- // OpenCode uses expires (milliseconds)
94
+ // Claude uses expiresAt (milliseconds, camelCase)
95
+ if (typeof data.expiresAt === "number") {
96
+ return data.expiresAt < SECONDS_THRESHOLD
97
+ ? data.expiresAt * 1000
98
+ : data.expiresAt;
99
+ }
100
+ // OpenCode uses expires (milliseconds, but apply heuristic for safety)
81
101
  if (typeof data.expires === "number") {
82
- return data.expires;
102
+ return data.expires < SECONDS_THRESHOLD
103
+ ? data.expires * 1000
104
+ : data.expires;
83
105
  }
84
106
  return undefined;
85
107
  }
86
- export { isCredentialExpired, isTokenExpired };
108
+ /**
109
+ * Extract expiry date from credential data.
110
+ *
111
+ * Convenience wrapper around {@link extractExpiryTimestamp} that returns
112
+ * a Date object instead of milliseconds.
113
+ *
114
+ * @param data - The credential data object
115
+ * @returns Date if expiry field found, undefined otherwise
116
+ */
117
+ function extractExpiryDate(data) {
118
+ const ts = extractExpiryTimestamp(data);
119
+ if (ts === undefined)
120
+ return undefined;
121
+ return new Date(ts);
122
+ }
123
+ /**
124
+ * Check if credential data contains a refresh token.
125
+ *
126
+ * Supports multiple field naming conventions:
127
+ * - `refresh_token`: Standard OAuth / Google / Generic
128
+ * - `refreshToken`: Claude (camelCase)
129
+ * - `refresh`: OpenCode
130
+ *
131
+ * @param data - The credential data object (accepts unknown for safety)
132
+ * @returns true if a refresh token field exists with a string value
133
+ */
134
+ function isRefreshable(data) {
135
+ if (typeof data !== "object" || data === null)
136
+ return false;
137
+ const record = data;
138
+ // Standard OAuth field name
139
+ if ("refresh_token" in record && typeof record.refresh_token === "string") {
140
+ return true;
141
+ }
142
+ // Claude uses camelCase
143
+ if ("refreshToken" in record && typeof record.refreshToken === "string") {
144
+ return true;
145
+ }
146
+ // OpenCode uses 'refresh'
147
+ if ("refresh" in record && typeof record.refresh === "string") {
148
+ return true;
149
+ }
150
+ return false;
151
+ }
152
+ export { extractExpiryDate, extractExpiryTimestamp, isCredentialExpired, isRefreshable, isTokenExpired, };
@@ -4,13 +4,15 @@
4
4
  * Refreshes credentials by running the agent in an isolated temp config,
5
5
  * triggering its internal auth refresh mechanism.
6
6
  */
7
- import type { Credentials } from "./types.js";
7
+ import { type Credentials } from "./types.js";
8
8
  /** Options for credential refresh */
9
9
  interface RefreshOptions {
10
10
  /** Timeout in ms (default: 30000) */
11
11
  timeout?: number;
12
12
  /** Provider for multi-provider agents (OpenCode) */
13
13
  provider?: string;
14
+ /** Storage type for persisting refreshed credentials (default: "file") */
15
+ storage?: "keychain" | "file";
14
16
  }
15
17
  /** Result of a credential refresh attempt */
16
18
  type RefreshResult = {
@@ -39,22 +41,46 @@ type RefreshAndPersistResult = {
39
41
  ok: false;
40
42
  staleCredentials: Credentials;
41
43
  };
44
+ /** Install function type for refreshAndPersist */
45
+ type InstallFunction = (creds: Credentials, options?: {
46
+ storage?: "keychain" | "file";
47
+ }) => {
48
+ ok: boolean;
49
+ message: string;
50
+ };
42
51
  /**
43
- * Refresh credentials and persist to original storage.
52
+ * Refresh credentials and persist to storage.
44
53
  *
45
54
  * Higher-level function that:
46
55
  * 1. Refreshes credentials via agent subprocess
47
- * 2. Preserves _source marker from original credentials
48
- * 3. Persists refreshed credentials to original storage location
56
+ * 2. Persists refreshed credentials to the specified storage location
49
57
  *
50
- * @param creds - Original credentials with _source marker
58
+ * @param creds - Original credentials
51
59
  * @param installFunction - Function to install credentials (avoids circular import)
52
- * @param options - Refresh options
60
+ * @param options - Refresh options including storage type
53
61
  * @returns Refreshed credentials or original credentials on failure
54
62
  */
55
- declare function refreshAndPersist(creds: Credentials, installFunction: (c: Credentials) => {
56
- ok: boolean;
57
- message: string;
58
- }, options?: RefreshOptions): Promise<RefreshAndPersistResult>;
59
- export { refreshAndPersist, refreshCredentials };
60
- export type { RefreshAndPersistResult, RefreshOptions, RefreshResult };
63
+ declare function refreshAndPersist(creds: Credentials, installFunction: InstallFunction, options?: RefreshOptions): Promise<RefreshAndPersistResult>;
64
+ /** Result of refreshing an opaque credential blob */
65
+ type RefreshBlobResult = {
66
+ ok: true;
67
+ blob: unknown;
68
+ expiresAt: Date | undefined;
69
+ } | {
70
+ ok: false;
71
+ error: string;
72
+ };
73
+ /**
74
+ * Refresh credentials from an opaque blob.
75
+ *
76
+ * This is the vault-friendly interface: accepts an opaque blob, validates it,
77
+ * refreshes the credentials, and returns the new blob. The caller (axvault)
78
+ * never needs to understand the blob's internal structure.
79
+ *
80
+ * @param blob - Opaque credential blob (expected to be a Credentials object)
81
+ * @param options - Refresh options (timeout)
82
+ * @returns RefreshBlobResult with new blob and expiresAt, or error
83
+ */
84
+ declare function refreshBlob(blob: unknown, options?: RefreshOptions): Promise<RefreshBlobResult>;
85
+ export { refreshAndPersist, refreshBlob, refreshCredentials };
86
+ export type { RefreshAndPersistResult, RefreshBlobResult, RefreshOptions, RefreshResult, };
@@ -7,8 +7,10 @@
7
7
  import { cleanupCredentials, runAgent } from "axexec";
8
8
  import { buildRefreshedCredentials } from "./build-refreshed-credentials.js";
9
9
  import { formatRunError } from "./format-run-error.js";
10
- import { mergeOpenCodeBundle, resolveRefreshCredentials, } from "./resolve-refresh-credentials.js";
10
+ import { extractExpiryDate } from "./is-token-expired.js";
11
+ import { resolveRefreshCredentials } from "./resolve-refresh-credentials.js";
11
12
  import { waitForRefreshedCredentials } from "./wait-for-refreshed-credentials.js";
13
+ import { parseCredentials } from "./types.js";
12
14
  /** Default timeout for refresh operations in milliseconds */
13
15
  const DEFAULT_REFRESH_TIMEOUT_MS = 30_000;
14
16
  async function safeCleanup(directory) {
@@ -33,7 +35,7 @@ async function safeCleanup(directory) {
33
35
  async function refreshCredentials(creds, options) {
34
36
  const timeoutMs = options?.timeout ?? DEFAULT_REFRESH_TIMEOUT_MS;
35
37
  const deadlineMs = Date.now() + timeoutMs;
36
- const resolved = resolveRefreshCredentials(creds, options);
38
+ const resolved = resolveRefreshCredentials(creds);
37
39
  if (!resolved.ok) {
38
40
  return { ok: false, error: resolved.error };
39
41
  }
@@ -88,13 +90,21 @@ async function refreshCredentials(creds, options) {
88
90
  if (!directories) {
89
91
  return { ok: false, error: "No directories in execution metadata" };
90
92
  }
91
- const refreshedCredentials = await waitForRefreshedCredentials(resolved.credentials.agent, directories, deadlineMs);
93
+ // For OpenCode, pass the provider to filter refreshed credentials
94
+ const provider = resolved.credentials.agent === "opencode"
95
+ ? resolved.credentials.provider
96
+ : undefined;
97
+ const refreshedCredentials = await waitForRefreshedCredentials(resolved.credentials.agent, directories, deadlineMs, { provider });
92
98
  if (!refreshedCredentials) {
93
99
  return { ok: false, error: "No credentials found after refresh" };
94
100
  }
101
+ const buildResult = buildRefreshedCredentials(creds, refreshedCredentials);
102
+ if (!buildResult.ok) {
103
+ return { ok: false, error: buildResult.error };
104
+ }
95
105
  return {
96
106
  ok: true,
97
- credentials: mergeOpenCodeBundle(creds, refreshedCredentials, resolved.mergeProvider),
107
+ credentials: buildResult.credentials,
98
108
  };
99
109
  }
100
110
  finally {
@@ -105,16 +115,15 @@ async function refreshCredentials(creds, options) {
105
115
  }
106
116
  }
107
117
  /**
108
- * Refresh credentials and persist to original storage.
118
+ * Refresh credentials and persist to storage.
109
119
  *
110
120
  * Higher-level function that:
111
121
  * 1. Refreshes credentials via agent subprocess
112
- * 2. Preserves _source marker from original credentials
113
- * 3. Persists refreshed credentials to original storage location
122
+ * 2. Persists refreshed credentials to the specified storage location
114
123
  *
115
- * @param creds - Original credentials with _source marker
124
+ * @param creds - Original credentials
116
125
  * @param installFunction - Function to install credentials (avoids circular import)
117
- * @param options - Refresh options
126
+ * @param options - Refresh options including storage type
118
127
  * @returns Refreshed credentials or original credentials on failure
119
128
  */
120
129
  async function refreshAndPersist(creds, installFunction, options) {
@@ -123,17 +132,39 @@ async function refreshAndPersist(creds, installFunction, options) {
123
132
  console.error(`Warning: Token refresh failed: ${result.error}`);
124
133
  return { ok: false, staleCredentials: creds };
125
134
  }
126
- // Build refreshed credentials, preserving _source and provider context
127
- const buildResult = buildRefreshedCredentials(creds, result.credentials);
128
- if (!buildResult.ok) {
129
- console.error(`Warning: ${buildResult.error}`);
130
- return { ok: false, staleCredentials: creds };
131
- }
132
135
  // Persist refreshed credentials back to storage
133
- const installResult = installFunction(buildResult.credentials);
136
+ const storage = options?.storage ?? "file";
137
+ const installResult = installFunction(result.credentials, { storage });
134
138
  if (!installResult.ok) {
135
139
  console.error(`Warning: Failed to save refreshed credentials: ${installResult.message}`);
136
140
  }
137
- return { ok: true, credentials: buildResult.credentials };
141
+ return { ok: true, credentials: result.credentials };
142
+ }
143
+ /**
144
+ * Refresh credentials from an opaque blob.
145
+ *
146
+ * This is the vault-friendly interface: accepts an opaque blob, validates it,
147
+ * refreshes the credentials, and returns the new blob. The caller (axvault)
148
+ * never needs to understand the blob's internal structure.
149
+ *
150
+ * @param blob - Opaque credential blob (expected to be a Credentials object)
151
+ * @param options - Refresh options (timeout)
152
+ * @returns RefreshBlobResult with new blob and expiresAt, or error
153
+ */
154
+ async function refreshBlob(blob, options) {
155
+ // Parse and validate the blob
156
+ const credentials = parseCredentials(blob);
157
+ if (!credentials) {
158
+ return { ok: false, error: "Invalid credential format" };
159
+ }
160
+ // Refresh using existing function
161
+ const result = await refreshCredentials(credentials, options);
162
+ if (!result.ok) {
163
+ return { ok: false, error: result.error };
164
+ }
165
+ // Extract expiry from the refreshed data
166
+ const expiresAt = extractExpiryDate(result.credentials.data);
167
+ // Return the new blob (the Credentials object itself)
168
+ return { ok: true, blob: result.credentials, expiresAt };
138
169
  }
139
- export { refreshAndPersist, refreshCredentials };
170
+ export { refreshAndPersist, refreshBlob, refreshCredentials };