@antseed/provider-core 0.1.0 → 0.1.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.
@@ -1,211 +0,0 @@
1
- import type { TokenProvider, TokenProviderState } from '@antseed/node';
2
-
3
- export type { TokenProvider, TokenProviderState };
4
-
5
- const REFRESH_BUFFER_MS = 5 * 60 * 1000; // refresh 5 min before expiry
6
- const DEFAULT_REFRESH_TIMEOUT_MS = 15_000;
7
- const DEFAULT_OAUTH_TOKEN_ENDPOINT = 'https://console.anthropic.com/v1/oauth/token';
8
-
9
- function getRefreshTimeoutMs(): number {
10
- const raw = process.env['ANTSEED_OAUTH_REFRESH_TIMEOUT_MS'];
11
- if (!raw) {
12
- return DEFAULT_REFRESH_TIMEOUT_MS;
13
- }
14
- const parsed = Number.parseInt(raw.trim(), 10);
15
- if (!Number.isFinite(parsed) || parsed <= 0) {
16
- return DEFAULT_REFRESH_TIMEOUT_MS;
17
- }
18
- return parsed;
19
- }
20
-
21
- // ---------------------------------------------------------------------------
22
- // StaticTokenProvider
23
- // ---------------------------------------------------------------------------
24
-
25
- /** Wraps a static API key. No refresh logic. */
26
- export class StaticTokenProvider implements TokenProvider {
27
- constructor(private readonly token: string) {}
28
- async getToken(): Promise<string> {
29
- return this.token;
30
- }
31
- stop(): void {}
32
- getState(): TokenProviderState {
33
- return { accessToken: this.token };
34
- }
35
- }
36
-
37
- // ---------------------------------------------------------------------------
38
- // OAuthTokenProvider
39
- // ---------------------------------------------------------------------------
40
-
41
- interface OAuthState {
42
- accessToken: string;
43
- refreshToken: string;
44
- expiresAt: number; // epoch ms
45
- }
46
-
47
- type RefreshRequestEncoding = 'form' | 'json';
48
-
49
- /**
50
- * Manages an OAuth access/refresh token pair.
51
- * Transparently refreshes the access token when it nears expiry.
52
- */
53
- export class OAuthTokenProvider implements TokenProvider {
54
- private state: OAuthState;
55
- private refreshPromise: Promise<string> | null = null;
56
- private readonly tokenEndpoint: string;
57
- private readonly requestEncoding: RefreshRequestEncoding;
58
- private readonly clientId: string | undefined;
59
-
60
- constructor(opts: {
61
- accessToken: string;
62
- refreshToken: string;
63
- expiresAt: number;
64
- tokenEndpoint?: string;
65
- requestEncoding?: RefreshRequestEncoding;
66
- clientId?: string;
67
- }) {
68
- this.state = {
69
- accessToken: opts.accessToken,
70
- refreshToken: opts.refreshToken,
71
- expiresAt: opts.expiresAt,
72
- };
73
- this.tokenEndpoint = opts.tokenEndpoint ?? DEFAULT_OAUTH_TOKEN_ENDPOINT;
74
- this.requestEncoding = opts.requestEncoding ?? 'form';
75
- this.clientId = opts.clientId;
76
- }
77
-
78
- async getToken(): Promise<string> {
79
- if (!this.isExpiringSoon()) {
80
- return this.state.accessToken;
81
- }
82
- // Deduplicate concurrent refresh calls
83
- if (!this.refreshPromise) {
84
- this.refreshPromise = this.refresh().finally(() => {
85
- this.refreshPromise = null;
86
- });
87
- }
88
- return this.refreshPromise;
89
- }
90
-
91
- stop(): void {}
92
-
93
- /** Expose current state for persistence. */
94
- getState(): TokenProviderState {
95
- return { ...this.state };
96
- }
97
-
98
- private isExpiringSoon(): boolean {
99
- return Date.now() >= this.state.expiresAt - REFRESH_BUFFER_MS;
100
- }
101
-
102
- private async refresh(): Promise<string> {
103
- const payload: Record<string, string> = {
104
- grant_type: 'refresh_token',
105
- refresh_token: this.state.refreshToken,
106
- };
107
- if (this.clientId) {
108
- payload['client_id'] = this.clientId;
109
- }
110
-
111
- const headers =
112
- this.requestEncoding === 'json'
113
- ? { 'Content-Type': 'application/json' }
114
- : { 'Content-Type': 'application/x-www-form-urlencoded' };
115
- const body =
116
- this.requestEncoding === 'json'
117
- ? JSON.stringify(payload)
118
- : new URLSearchParams(payload).toString();
119
-
120
- const timeoutMs = getRefreshTimeoutMs();
121
- const controller = new AbortController();
122
- const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
123
-
124
- let res: Response;
125
- try {
126
- res = await fetch(this.tokenEndpoint, {
127
- method: 'POST',
128
- headers,
129
- body,
130
- signal: controller.signal,
131
- });
132
- } catch (err) {
133
- const message = err instanceof Error ? err.message : String(err);
134
- if (err instanceof Error && err.name === 'AbortError') {
135
- throw new Error(
136
- `OAuth refresh timed out after ${timeoutMs}ms while reaching ${this.tokenEndpoint}. ` +
137
- 'Check network/proxy/firewall access or use apikey auth.'
138
- );
139
- }
140
- throw new Error(`OAuth refresh request failed: ${message}`);
141
- } finally {
142
- clearTimeout(timeoutHandle);
143
- }
144
-
145
- if (!res.ok) {
146
- const text = await res.text();
147
- throw new Error(`OAuth refresh failed (${res.status}): ${text}`);
148
- }
149
-
150
- const data = (await res.json()) as {
151
- access_token?: string;
152
- accessToken?: string;
153
- refresh_token?: string;
154
- refreshToken?: string;
155
- expires_in?: number;
156
- expires_at?: number;
157
- expiresAt?: number;
158
- };
159
-
160
- const newAccess = data.access_token ?? data.accessToken;
161
- if (!newAccess) {
162
- throw new Error('OAuth refresh response missing access token');
163
- }
164
-
165
- this.state.accessToken = newAccess;
166
- if (data.refresh_token ?? data.refreshToken) {
167
- this.state.refreshToken = (data.refresh_token ?? data.refreshToken)!;
168
- }
169
- if (data.expires_at ?? data.expiresAt) {
170
- this.state.expiresAt = (data.expires_at ?? data.expiresAt)!;
171
- } else if (data.expires_in) {
172
- this.state.expiresAt = Date.now() + data.expires_in * 1000;
173
- }
174
-
175
- return this.state.accessToken;
176
- }
177
- }
178
-
179
- // ---------------------------------------------------------------------------
180
- // Factory
181
- // ---------------------------------------------------------------------------
182
-
183
- export type AuthType = 'apikey' | 'oauth';
184
-
185
- /**
186
- * Create the appropriate TokenProvider from config values.
187
- */
188
- export function createTokenProvider(opts: {
189
- authType?: AuthType;
190
- authValue: string;
191
- refreshToken?: string;
192
- expiresAt?: number;
193
- }): TokenProvider {
194
- const authType = opts.authType ?? 'apikey';
195
-
196
- switch (authType) {
197
- case 'oauth':
198
- if (!opts.refreshToken) {
199
- // No refresh token — treat as static (works until expiry)
200
- return new StaticTokenProvider(opts.authValue);
201
- }
202
- return new OAuthTokenProvider({
203
- accessToken: opts.authValue,
204
- refreshToken: opts.refreshToken,
205
- expiresAt: opts.expiresAt ?? Date.now() + 3600_000,
206
- });
207
-
208
- default:
209
- return new StaticTokenProvider(opts.authValue);
210
- }
211
- }
package/tsconfig.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src"],
8
- "exclude": ["src/**/*.test.ts"]
9
- }