@supaku/agentfactory-linear 0.7.13 → 0.7.14

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,5 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { LinearAgentClient } from './agent-client.js';
3
+ import { TokenBucket } from './rate-limiter.js';
3
4
  // ---------------------------------------------------------------------------
4
5
  // Mock helpers
5
6
  // ---------------------------------------------------------------------------
@@ -28,6 +29,7 @@ function createClientWithProject(project) {
28
29
  value: { maxRetries: 0, baseDelay: 0, maxDelay: 0 },
29
30
  writable: false,
30
31
  });
32
+ Object.defineProperty(client, 'rateLimiter', { value: new TokenBucket(), writable: false });
31
33
  Object.defineProperty(client, 'statusCache', { value: new Map(), writable: false });
32
34
  return client;
33
35
  }
@@ -8,6 +8,7 @@ import type { LinearAgentClientConfig, LinearWorkflowStatus, StatusMapping, Agen
8
8
  export declare class LinearAgentClient {
9
9
  private readonly client;
10
10
  private readonly retryConfig;
11
+ private readonly rateLimiter;
11
12
  private statusCache;
12
13
  constructor(config: LinearAgentClientConfig);
13
14
  /**
@@ -15,7 +16,11 @@ export declare class LinearAgentClient {
15
16
  */
16
17
  get linearClient(): LinearClient;
17
18
  /**
18
- * Execute an operation with retry logic
19
+ * Execute an operation with retry logic and rate limiting.
20
+ *
21
+ * On HTTP 429 (rate limited):
22
+ * - Uses the Retry-After header value for the wait delay (falls back to 60s)
23
+ * - Penalizes the token bucket so concurrent callers also back off
19
24
  */
20
25
  private withRetry;
21
26
  /**
@@ -158,6 +163,27 @@ export declare class LinearAgentClient {
158
163
  * @returns True if the issue has a parent issue
159
164
  */
160
165
  isChildIssue(issueIdOrIdentifier: string): Promise<boolean>;
166
+ /**
167
+ * Fetch all non-terminal issues in a project using a single GraphQL query.
168
+ *
169
+ * Replaces the N+1 pattern of fetching issues then lazy-loading state/labels/parent/project
170
+ * for each one. Returns pre-resolved data suitable for GovernorIssue construction.
171
+ *
172
+ * @param project - Linear project name
173
+ * @returns Array of issue data with childCount for parent detection
174
+ */
175
+ listProjectIssues(project: string): Promise<Array<{
176
+ id: string;
177
+ identifier: string;
178
+ title: string;
179
+ description?: string;
180
+ status: string;
181
+ labels: string[];
182
+ createdAt: number;
183
+ parentId?: string;
184
+ project?: string;
185
+ childCount: number;
186
+ }>>;
161
187
  /**
162
188
  * Check if an issue has child issues (is a parent issue)
163
189
  *
@@ -1 +1 @@
1
- {"version":3,"file":"agent-client.d.ts","sourceRoot":"","sources":["../../src/agent-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-client.d.ts","sourceRoot":"","sources":["../../src/agent-client.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { LinearClient, AgentActivitySignal as LinearAgentActivitySignal, IssueRelationType as LinearIssueRelationType, } from '@linear/sdk';
2
2
  import { LinearApiError, LinearStatusTransitionError } from './errors.js';
3
3
  import { withRetry, DEFAULT_RETRY_CONFIG } from './retry.js';
4
+ import { TokenBucket, extractRetryAfterMs } from './rate-limiter.js';
4
5
  /**
5
6
  * Core Linear Agent Client
6
7
  * Wraps @linear/sdk with retry logic and helper methods
@@ -8,6 +9,7 @@ import { withRetry, DEFAULT_RETRY_CONFIG } from './retry.js';
8
9
  export class LinearAgentClient {
9
10
  client;
10
11
  retryConfig;
12
+ rateLimiter;
11
13
  statusCache = new Map();
12
14
  constructor(config) {
13
15
  this.client = new LinearClient({
@@ -18,6 +20,7 @@ export class LinearAgentClient {
18
20
  ...DEFAULT_RETRY_CONFIG,
19
21
  ...config.retry,
20
22
  };
23
+ this.rateLimiter = new TokenBucket(config.rateLimit);
21
24
  }
22
25
  /**
23
26
  * Get the underlying LinearClient instance
@@ -26,11 +29,24 @@ export class LinearAgentClient {
26
29
  return this.client;
27
30
  }
28
31
  /**
29
- * Execute an operation with retry logic
32
+ * Execute an operation with retry logic and rate limiting.
33
+ *
34
+ * On HTTP 429 (rate limited):
35
+ * - Uses the Retry-After header value for the wait delay (falls back to 60s)
36
+ * - Penalizes the token bucket so concurrent callers also back off
30
37
  */
31
38
  async withRetry(fn) {
32
- return withRetry(fn, {
39
+ return withRetry(async () => {
40
+ await this.rateLimiter.acquire();
41
+ return fn();
42
+ }, {
33
43
  config: this.retryConfig,
44
+ getRetryAfterMs: extractRetryAfterMs,
45
+ onRateLimited: (retryAfterMs) => {
46
+ const seconds = retryAfterMs / 1000;
47
+ console.warn(`[LinearAgentClient] Rate limited by Linear API, backing off ${seconds}s`);
48
+ this.rateLimiter.penalize(seconds);
49
+ },
34
50
  onRetry: ({ attempt, delay }) => {
35
51
  console.log(`[LinearAgentClient] Retry attempt ${attempt + 1}/${this.retryConfig.maxRetries}, ` +
36
52
  `waiting ${delay}ms`);
@@ -422,6 +438,56 @@ export class LinearAgentClient {
422
438
  return parent != null;
423
439
  });
424
440
  }
441
+ /**
442
+ * Fetch all non-terminal issues in a project using a single GraphQL query.
443
+ *
444
+ * Replaces the N+1 pattern of fetching issues then lazy-loading state/labels/parent/project
445
+ * for each one. Returns pre-resolved data suitable for GovernorIssue construction.
446
+ *
447
+ * @param project - Linear project name
448
+ * @returns Array of issue data with childCount for parent detection
449
+ */
450
+ async listProjectIssues(project) {
451
+ await this.rateLimiter.acquire();
452
+ const query = `
453
+ query ListProjectIssues($filter: IssueFilter!) {
454
+ issues(filter: $filter, first: 250) {
455
+ nodes {
456
+ id
457
+ identifier
458
+ title
459
+ description
460
+ createdAt
461
+ state { name }
462
+ labels { nodes { name } }
463
+ parent { id }
464
+ project { name }
465
+ children { nodes { id } }
466
+ }
467
+ }
468
+ }
469
+ `;
470
+ const terminalStatuses = ['Accepted', 'Canceled', 'Duplicate'];
471
+ const result = await this.client.client.rawRequest(query, {
472
+ filter: {
473
+ project: { name: { eq: project } },
474
+ state: { name: { nin: terminalStatuses } },
475
+ },
476
+ });
477
+ const data = result.data;
478
+ return data.issues.nodes.map((node) => ({
479
+ id: node.id,
480
+ identifier: node.identifier,
481
+ title: node.title,
482
+ description: node.description ?? undefined,
483
+ status: node.state?.name ?? 'Backlog',
484
+ labels: node.labels.nodes.map((l) => l.name),
485
+ createdAt: new Date(node.createdAt).getTime(),
486
+ parentId: node.parent?.id ?? undefined,
487
+ project: node.project?.name ?? undefined,
488
+ childCount: node.children.nodes.length,
489
+ }));
490
+ }
425
491
  /**
426
492
  * Check if an issue has child issues (is a parent issue)
427
493
  *
@@ -10,6 +10,8 @@ export { truncateText, buildCompletionComment, splitContentIntoComments, buildCo
10
10
  export type { CommentChunk } from './utils.js';
11
11
  export { parseCheckboxes, updateCheckbox, updateCheckboxByText, updateCheckboxes, hasCheckboxes, getCheckboxSummary, } from './checkbox-utils.js';
12
12
  export type { CheckboxItem, CheckboxUpdate } from './checkbox-utils.js';
13
+ export { TokenBucket, DEFAULT_RATE_LIMIT_CONFIG, extractRetryAfterMs } from './rate-limiter.js';
14
+ export type { TokenBucketConfig } from './rate-limiter.js';
13
15
  export { LinearAgentClient, createLinearAgentClient } from './agent-client.js';
14
16
  export { AgentSession, createAgentSession } from './agent-session.js';
15
17
  export * from './webhook-types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
package/dist/src/index.js CHANGED
@@ -10,6 +10,8 @@ export { LINEAR_COMMENT_MAX_LENGTH, TRUNCATION_MARKER, MAX_COMPLETION_COMMENTS,
10
10
  export { truncateText, buildCompletionComment, splitContentIntoComments, buildCompletionComments, } from './utils.js';
11
11
  // Checkbox utilities
12
12
  export { parseCheckboxes, updateCheckbox, updateCheckboxByText, updateCheckboxes, hasCheckboxes, getCheckboxSummary, } from './checkbox-utils.js';
13
+ // Rate limiter
14
+ export { TokenBucket, DEFAULT_RATE_LIMIT_CONFIG, extractRetryAfterMs } from './rate-limiter.js';
13
15
  // Client
14
16
  export { LinearAgentClient, createLinearAgentClient } from './agent-client.js';
15
17
  // Session
@@ -94,14 +94,27 @@ export declare class LinearPlatformAdapter extends LinearFrontendAdapter {
94
94
  /**
95
95
  * Scan a Linear project for all non-terminal issues.
96
96
  *
97
- * Queries the Linear API with a filter that excludes terminal statuses
98
- * (Accepted, Canceled, Duplicate). Each issue is converted to a
99
- * GovernorIssue for evaluation by the Governor.
97
+ * Uses a single GraphQL query via `listProjectIssues()` to fetch all
98
+ * issue data in one API call, eliminating the N+1 problem of lazy-loading
99
+ * state/labels/parent/project for each issue.
100
100
  *
101
101
  * @param project - Linear project name to scan
102
102
  * @returns Array of GovernorIssue for all active issues
103
103
  */
104
104
  scanProjectIssues(project: string): Promise<GovernorIssue[]>;
105
+ /**
106
+ * Scan a project and return both GovernorIssues and a set of parent issue IDs.
107
+ *
108
+ * The parent issue IDs are derived from `childCount > 0` in the single
109
+ * GraphQL query, allowing callers to skip per-issue `isParentIssue()` API calls.
110
+ *
111
+ * @param project - Linear project name to scan
112
+ * @returns Issues and a set of parent issue IDs
113
+ */
114
+ scanProjectIssuesWithParents(project: string): Promise<{
115
+ issues: GovernorIssue[];
116
+ parentIssueIds: Set<string>;
117
+ }>;
105
118
  /**
106
119
  * Convert a Linear SDK Issue object to a GovernorIssue.
107
120
  *
@@ -1 +1 @@
1
- {"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../../src/platform-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAY1D;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,iCAAiC;AACjC,KAAK,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEhD;;;GAGG;AACH,UAAU,uBAAuB;IAC/B,IAAI,EAAE,sBAAsB,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,aAAa,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED;;;GAGG;AACH,UAAU,iBAAiB;IACzB,IAAI,EAAE,eAAe,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,aAAa,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,gDAAgD;AAChD,KAAK,aAAa,GAAG,uBAAuB,GAAG,iBAAiB,CAAA;AAmJhE;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,SAAQ,qBAAqB;IAC9D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;gBAEzC,MAAM,EAAE,iBAAiB;IAOrC;;;;;;;;;;;;OAYG;IACH,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,IAAI;IA0D/D;;;;;;;;;OASG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAkBlE;;;;;;;;;;OAUG;IACG,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;CAS/D"}
1
+ {"version":3,"file":"platform-adapter.d.ts","sourceRoot":"","sources":["../../src/platform-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAY1D;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,iCAAiC;AACjC,KAAK,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEhD;;;GAGG;AACH,UAAU,uBAAuB;IAC/B,IAAI,EAAE,sBAAsB,CAAA;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,aAAa,CAAA;IACpB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED;;;GAGG;AACH,UAAU,iBAAiB;IACzB,IAAI,EAAE,eAAe,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,aAAa,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACpB;AAED,gDAAgD;AAChD,KAAK,aAAa,GAAG,uBAAuB,GAAG,iBAAiB,CAAA;AAmJhE;;;;;;;;;GASG;AACH,qBAAa,qBAAsB,SAAQ,qBAAqB;IAC9D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAmB;gBAEzC,MAAM,EAAE,iBAAiB;IAOrC;;;;;;;;;;;;OAYG;IACH,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,EAAE,GAAG,IAAI;IA0D/D;;;;;;;;;OASG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAgBlE;;;;;;;;OAQG;IACG,4BAA4B,CAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC;IA0BpE;;;;;;;;;;OAUG;IACG,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;CAS/D"}
@@ -187,26 +187,57 @@ export class LinearPlatformAdapter extends LinearFrontendAdapter {
187
187
  /**
188
188
  * Scan a Linear project for all non-terminal issues.
189
189
  *
190
- * Queries the Linear API with a filter that excludes terminal statuses
191
- * (Accepted, Canceled, Duplicate). Each issue is converted to a
192
- * GovernorIssue for evaluation by the Governor.
190
+ * Uses a single GraphQL query via `listProjectIssues()` to fetch all
191
+ * issue data in one API call, eliminating the N+1 problem of lazy-loading
192
+ * state/labels/parent/project for each issue.
193
193
  *
194
194
  * @param project - Linear project name to scan
195
195
  * @returns Array of GovernorIssue for all active issues
196
196
  */
197
197
  async scanProjectIssues(project) {
198
- const linearClient = this.linearAgentClient.linearClient;
199
- const issueConnection = await linearClient.issues({
200
- filter: {
201
- project: { name: { eq: project } },
202
- state: { name: { nin: [...TERMINAL_STATUSES] } },
203
- },
204
- });
205
- const results = [];
206
- for (const issue of issueConnection.nodes) {
207
- results.push(await sdkIssueToGovernorIssue(issue));
198
+ const issues = await this.linearAgentClient.listProjectIssues(project);
199
+ return issues.map((issue) => ({
200
+ id: issue.id,
201
+ identifier: issue.identifier,
202
+ title: issue.title,
203
+ description: issue.description,
204
+ status: issue.status,
205
+ labels: issue.labels,
206
+ createdAt: issue.createdAt,
207
+ parentId: issue.parentId,
208
+ project: issue.project,
209
+ }));
210
+ }
211
+ /**
212
+ * Scan a project and return both GovernorIssues and a set of parent issue IDs.
213
+ *
214
+ * The parent issue IDs are derived from `childCount > 0` in the single
215
+ * GraphQL query, allowing callers to skip per-issue `isParentIssue()` API calls.
216
+ *
217
+ * @param project - Linear project name to scan
218
+ * @returns Issues and a set of parent issue IDs
219
+ */
220
+ async scanProjectIssuesWithParents(project) {
221
+ const rawIssues = await this.linearAgentClient.listProjectIssues(project);
222
+ const parentIssueIds = new Set();
223
+ const issues = [];
224
+ for (const issue of rawIssues) {
225
+ if (issue.childCount > 0) {
226
+ parentIssueIds.add(issue.id);
227
+ }
228
+ issues.push({
229
+ id: issue.id,
230
+ identifier: issue.identifier,
231
+ title: issue.title,
232
+ description: issue.description,
233
+ status: issue.status,
234
+ labels: issue.labels,
235
+ createdAt: issue.createdAt,
236
+ parentId: issue.parentId,
237
+ project: issue.project,
238
+ });
208
239
  }
209
- return results;
240
+ return { issues, parentIssueIds };
210
241
  }
211
242
  /**
212
243
  * Convert a Linear SDK Issue object to a GovernorIssue.
@@ -51,6 +51,7 @@ function createMockClient() {
51
51
  createAgentSessionOnIssue: vi.fn(),
52
52
  updateAgentSession: vi.fn(),
53
53
  createAgentActivity: vi.fn(),
54
+ listProjectIssues: vi.fn(),
54
55
  };
55
56
  const linearClient = {
56
57
  issues: vi.fn(),
@@ -290,13 +291,28 @@ describe('LinearPlatformAdapter', () => {
290
291
  // scanProjectIssues
291
292
  // ========================================================================
292
293
  describe('scanProjectIssues', () => {
294
+ /** Helper: create a listProjectIssues result entry */
295
+ function mockListProjectIssue(overrides = {}) {
296
+ return {
297
+ id: overrides.id ?? 'issue-uuid-1',
298
+ identifier: overrides.identifier ?? 'SUP-100',
299
+ title: overrides.title ?? 'Test Issue',
300
+ description: overrides.description ?? 'A test issue description',
301
+ status: overrides.status ?? 'Backlog',
302
+ labels: overrides.labels ?? ['Feature'],
303
+ createdAt: overrides.createdAt ?? Date.now(),
304
+ parentId: overrides.parentId,
305
+ project: overrides.project ?? 'MyProject',
306
+ childCount: overrides.childCount ?? 0,
307
+ };
308
+ }
293
309
  it('returns GovernorIssues for all non-terminal issues', async () => {
294
310
  const issues = [
295
- mockLinearIssue({ id: 'i-1', identifier: 'SUP-1', stateName: 'Backlog' }),
296
- mockLinearIssue({ id: 'i-2', identifier: 'SUP-2', stateName: 'Started' }),
297
- mockLinearIssue({ id: 'i-3', identifier: 'SUP-3', stateName: 'Finished' }),
311
+ mockListProjectIssue({ id: 'i-1', identifier: 'SUP-1', status: 'Backlog' }),
312
+ mockListProjectIssue({ id: 'i-2', identifier: 'SUP-2', status: 'Started' }),
313
+ mockListProjectIssue({ id: 'i-3', identifier: 'SUP-3', status: 'Finished' }),
298
314
  ];
299
- mocks.linearClientIssues.mockResolvedValue({ nodes: issues });
315
+ mocks.listProjectIssues.mockResolvedValue(issues);
300
316
  const result = await adapter.scanProjectIssues('MyProject');
301
317
  expect(result).toHaveLength(3);
302
318
  expect(result[0].id).toBe('i-1');
@@ -307,40 +323,57 @@ describe('LinearPlatformAdapter', () => {
307
323
  expect(result[2].id).toBe('i-3');
308
324
  expect(result[2].status).toBe('Finished');
309
325
  });
310
- it('passes correct filter to Linear API', async () => {
311
- mocks.linearClientIssues.mockResolvedValue({ nodes: [] });
326
+ it('delegates to listProjectIssues with project name', async () => {
327
+ mocks.listProjectIssues.mockResolvedValue([]);
312
328
  await adapter.scanProjectIssues('TestProject');
313
- expect(mocks.linearClientIssues).toHaveBeenCalledWith({
314
- filter: {
315
- project: { name: { eq: 'TestProject' } },
316
- state: { name: { nin: ['Accepted', 'Canceled', 'Duplicate'] } },
317
- },
318
- });
329
+ expect(mocks.listProjectIssues).toHaveBeenCalledWith('TestProject');
319
330
  });
320
331
  it('returns empty array when no issues found', async () => {
321
- mocks.linearClientIssues.mockResolvedValue({ nodes: [] });
332
+ mocks.listProjectIssues.mockResolvedValue([]);
322
333
  const result = await adapter.scanProjectIssues('EmptyProject');
323
334
  expect(result).toEqual([]);
324
335
  });
325
- it('resolves lazy-loaded issue properties', async () => {
326
- const issue = mockLinearIssue({
336
+ it('maps issue properties correctly', async () => {
337
+ const issue = mockListProjectIssue({
327
338
  labels: ['Bug', 'Urgent'],
328
339
  parentId: 'parent-1',
329
- projectName: 'MyProject',
340
+ project: 'MyProject',
330
341
  });
331
- mocks.linearClientIssues.mockResolvedValue({ nodes: [issue] });
342
+ mocks.listProjectIssues.mockResolvedValue([issue]);
332
343
  const result = await adapter.scanProjectIssues('MyProject');
333
344
  expect(result[0].labels).toEqual(['Bug', 'Urgent']);
334
345
  expect(result[0].parentId).toBe('parent-1');
335
346
  expect(result[0].project).toBe('MyProject');
336
347
  });
337
- it('converts createdAt Date to epoch milliseconds', async () => {
338
- const issue = mockLinearIssue({
339
- createdAt: new Date('2025-03-01T08:00:00Z'),
340
- });
341
- mocks.linearClientIssues.mockResolvedValue({ nodes: [issue] });
348
+ it('preserves createdAt as epoch milliseconds', async () => {
349
+ const createdAt = new Date('2025-03-01T08:00:00Z').getTime();
350
+ const issue = mockListProjectIssue({ createdAt });
351
+ mocks.listProjectIssues.mockResolvedValue([issue]);
342
352
  const result = await adapter.scanProjectIssues('MyProject');
343
- expect(result[0].createdAt).toBe(new Date('2025-03-01T08:00:00Z').getTime());
353
+ expect(result[0].createdAt).toBe(createdAt);
354
+ });
355
+ });
356
+ // ========================================================================
357
+ // scanProjectIssuesWithParents
358
+ // ========================================================================
359
+ describe('scanProjectIssuesWithParents', () => {
360
+ it('returns issues and parent IDs set', async () => {
361
+ mocks.listProjectIssues.mockResolvedValue([
362
+ {
363
+ id: 'p-1', identifier: 'SUP-10', title: 'Parent',
364
+ status: 'Backlog', labels: [], createdAt: Date.now(),
365
+ project: 'MyProject', childCount: 3,
366
+ },
367
+ {
368
+ id: 'c-1', identifier: 'SUP-11', title: 'Child',
369
+ status: 'Backlog', labels: [], createdAt: Date.now(),
370
+ parentId: 'p-1', project: 'MyProject', childCount: 0,
371
+ },
372
+ ]);
373
+ const { issues, parentIssueIds } = await adapter.scanProjectIssuesWithParents('MyProject');
374
+ expect(issues).toHaveLength(2);
375
+ expect(parentIssueIds.has('p-1')).toBe(true);
376
+ expect(parentIssueIds.has('c-1')).toBe(false);
344
377
  });
345
378
  });
346
379
  // ========================================================================
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Token Bucket Rate Limiter
3
+ *
4
+ * Proactive rate limiting for Linear API calls. Uses a token bucket algorithm
5
+ * to throttle requests below Linear's ~100 req/min limit.
6
+ *
7
+ * Default: 80 burst capacity, 1.5 tokens/sec refill (~90 req/min sustained).
8
+ */
9
+ export interface TokenBucketConfig {
10
+ /** Maximum tokens (burst capacity). Default: 80 */
11
+ maxTokens: number;
12
+ /** Tokens added per second. Default: 1.5 (~90/min) */
13
+ refillRate: number;
14
+ }
15
+ export declare const DEFAULT_RATE_LIMIT_CONFIG: TokenBucketConfig;
16
+ export declare class TokenBucket {
17
+ private tokens;
18
+ private readonly maxTokens;
19
+ private readonly refillRate;
20
+ private lastRefill;
21
+ private waitQueue;
22
+ constructor(config?: Partial<TokenBucketConfig>);
23
+ /** Refill tokens based on elapsed time since last refill. */
24
+ private refill;
25
+ /** Drain waiters that can be satisfied after a refill. */
26
+ private drainWaiters;
27
+ /**
28
+ * Acquire a single token. Resolves immediately if tokens are available,
29
+ * otherwise queues the caller until a token becomes available via refill.
30
+ */
31
+ acquire(): Promise<void>;
32
+ /** Schedule a timer to refill and drain waiters. */
33
+ private refillTimer;
34
+ private scheduleRefillDrain;
35
+ /**
36
+ * Penalize the bucket after receiving a 429 rate limit response.
37
+ *
38
+ * Drains all tokens to 0 and shifts the refill baseline forward by
39
+ * `seconds` so no new tokens appear until the penalty expires.
40
+ * Any already-queued waiters will wait for the penalty period plus
41
+ * normal refill time.
42
+ *
43
+ * @param seconds - How long to pause before tokens start refilling (from Retry-After header)
44
+ */
45
+ penalize(seconds: number): void;
46
+ /** Current number of available tokens (for testing/monitoring). */
47
+ get availableTokens(): number;
48
+ /** Number of callers waiting for tokens (for testing/monitoring). */
49
+ get pendingCount(): number;
50
+ }
51
+ /**
52
+ * Extract a Retry-After delay (in milliseconds) from an error thrown by
53
+ * the Linear SDK or a raw HTTP 429 response.
54
+ *
55
+ * Checks (in order):
56
+ * 1. `error.response.headers.get('retry-after')` (fetch Response)
57
+ * 2. `error.response.headers['retry-after']` (plain object headers)
58
+ * 3. `error.headers?.['retry-after']` (error-level headers)
59
+ *
60
+ * The Retry-After value is parsed as seconds (integer). If no valid value
61
+ * is found, returns `null`.
62
+ */
63
+ export declare function extractRetryAfterMs(error: unknown): number | null;
64
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,yBAAyB,EAAE,iBAGvC,CAAA;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,SAAS,CAAwB;gBAE7B,MAAM,GAAE,OAAO,CAAC,iBAAiB,CAAM;IAQnD,6DAA6D;IAC7D,OAAO,CAAC,MAAM;IAad,0DAA0D;IAC1D,OAAO,CAAC,YAAY;IAQpB;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B,oDAAoD;IACpD,OAAO,CAAC,WAAW,CAA6C;IAEhE,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO/B,mEAAmE;IACnE,IAAI,eAAe,IAAI,MAAM,CAG5B;IAED,qEAAqE;IACrE,IAAI,YAAY,IAAI,MAAM,CAEzB;CACF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwBjE"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Token Bucket Rate Limiter
3
+ *
4
+ * Proactive rate limiting for Linear API calls. Uses a token bucket algorithm
5
+ * to throttle requests below Linear's ~100 req/min limit.
6
+ *
7
+ * Default: 80 burst capacity, 1.5 tokens/sec refill (~90 req/min sustained).
8
+ */
9
+ export const DEFAULT_RATE_LIMIT_CONFIG = {
10
+ maxTokens: 80,
11
+ refillRate: 1.5,
12
+ };
13
+ export class TokenBucket {
14
+ tokens;
15
+ maxTokens;
16
+ refillRate;
17
+ lastRefill;
18
+ waitQueue = [];
19
+ constructor(config = {}) {
20
+ const resolved = { ...DEFAULT_RATE_LIMIT_CONFIG, ...config };
21
+ this.maxTokens = resolved.maxTokens;
22
+ this.refillRate = resolved.refillRate;
23
+ this.tokens = this.maxTokens;
24
+ this.lastRefill = Date.now();
25
+ }
26
+ /** Refill tokens based on elapsed time since last refill. */
27
+ refill() {
28
+ const now = Date.now();
29
+ const elapsed = (now - this.lastRefill) / 1000;
30
+ // During a penalty period, lastRefill is in the future so elapsed is negative.
31
+ // Skip refill entirely until the penalty expires.
32
+ if (elapsed <= 0)
33
+ return;
34
+ const newTokens = elapsed * this.refillRate;
35
+ this.tokens = Math.min(this.maxTokens, this.tokens + newTokens);
36
+ this.lastRefill = now;
37
+ }
38
+ /** Drain waiters that can be satisfied after a refill. */
39
+ drainWaiters() {
40
+ while (this.waitQueue.length > 0 && this.tokens >= 1) {
41
+ this.tokens -= 1;
42
+ const resolve = this.waitQueue.shift();
43
+ resolve();
44
+ }
45
+ }
46
+ /**
47
+ * Acquire a single token. Resolves immediately if tokens are available,
48
+ * otherwise queues the caller until a token becomes available via refill.
49
+ */
50
+ async acquire() {
51
+ this.refill();
52
+ if (this.tokens >= 1 && this.waitQueue.length === 0) {
53
+ this.tokens -= 1;
54
+ return;
55
+ }
56
+ return new Promise((resolve) => {
57
+ this.waitQueue.push(resolve);
58
+ this.scheduleRefillDrain();
59
+ });
60
+ }
61
+ /** Schedule a timer to refill and drain waiters. */
62
+ refillTimer = null;
63
+ scheduleRefillDrain() {
64
+ if (this.refillTimer !== null)
65
+ return;
66
+ // Time until 1 token is available
67
+ const msPerToken = 1000 / this.refillRate;
68
+ this.refillTimer = setTimeout(() => {
69
+ this.refillTimer = null;
70
+ this.refill();
71
+ this.drainWaiters();
72
+ // If there are still waiters, schedule again
73
+ if (this.waitQueue.length > 0) {
74
+ this.scheduleRefillDrain();
75
+ }
76
+ }, msPerToken);
77
+ }
78
+ /**
79
+ * Penalize the bucket after receiving a 429 rate limit response.
80
+ *
81
+ * Drains all tokens to 0 and shifts the refill baseline forward by
82
+ * `seconds` so no new tokens appear until the penalty expires.
83
+ * Any already-queued waiters will wait for the penalty period plus
84
+ * normal refill time.
85
+ *
86
+ * @param seconds - How long to pause before tokens start refilling (from Retry-After header)
87
+ */
88
+ penalize(seconds) {
89
+ this.tokens = 0;
90
+ // Push lastRefill into the future so refill() computes negative elapsed
91
+ // time until the penalty expires, effectively freezing token generation.
92
+ this.lastRefill = Date.now() + seconds * 1000;
93
+ }
94
+ /** Current number of available tokens (for testing/monitoring). */
95
+ get availableTokens() {
96
+ this.refill();
97
+ return Math.floor(this.tokens);
98
+ }
99
+ /** Number of callers waiting for tokens (for testing/monitoring). */
100
+ get pendingCount() {
101
+ return this.waitQueue.length;
102
+ }
103
+ }
104
+ /**
105
+ * Extract a Retry-After delay (in milliseconds) from an error thrown by
106
+ * the Linear SDK or a raw HTTP 429 response.
107
+ *
108
+ * Checks (in order):
109
+ * 1. `error.response.headers.get('retry-after')` (fetch Response)
110
+ * 2. `error.response.headers['retry-after']` (plain object headers)
111
+ * 3. `error.headers?.['retry-after']` (error-level headers)
112
+ *
113
+ * The Retry-After value is parsed as seconds (integer). If no valid value
114
+ * is found, returns `null`.
115
+ */
116
+ export function extractRetryAfterMs(error) {
117
+ if (typeof error !== 'object' || error === null)
118
+ return null;
119
+ const err = error;
120
+ // Check if this is a rate limit error (status 429)
121
+ const status = err.status ??
122
+ err.statusCode ??
123
+ err.response?.status;
124
+ if (status !== 429)
125
+ return null;
126
+ // Try to extract Retry-After from various locations
127
+ const headerValue = getRetryAfterHeader(err);
128
+ if (headerValue === null) {
129
+ // No Retry-After header — use a sensible default of 60s for Linear
130
+ return 60_000;
131
+ }
132
+ const seconds = parseInt(headerValue, 10);
133
+ if (Number.isNaN(seconds) || seconds <= 0)
134
+ return 60_000;
135
+ return seconds * 1000;
136
+ }
137
+ function getRetryAfterHeader(err) {
138
+ // error.response.headers.get('retry-after') — fetch-style Response
139
+ const response = err.response;
140
+ if (response) {
141
+ const headers = response.headers;
142
+ if (headers) {
143
+ // Headers object with .get() method (fetch API)
144
+ if (typeof headers.get === 'function') {
145
+ const val = headers.get('retry-after');
146
+ if (val)
147
+ return val;
148
+ }
149
+ // Plain object headers
150
+ const val = headers['retry-after'];
151
+ if (val)
152
+ return val;
153
+ }
154
+ }
155
+ // error.headers['retry-after']
156
+ const errorHeaders = err.headers;
157
+ if (errorHeaders) {
158
+ const val = errorHeaders['retry-after'];
159
+ if (val)
160
+ return val;
161
+ }
162
+ return null;
163
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rate-limiter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.test.d.ts","sourceRoot":"","sources":["../../src/rate-limiter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,217 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { TokenBucket, DEFAULT_RATE_LIMIT_CONFIG, extractRetryAfterMs } from './rate-limiter.js';
3
+ describe('TokenBucket', () => {
4
+ beforeEach(() => {
5
+ vi.useFakeTimers();
6
+ });
7
+ afterEach(() => {
8
+ vi.useRealTimers();
9
+ });
10
+ // ========================================================================
11
+ // Construction & defaults
12
+ // ========================================================================
13
+ it('uses default config when none provided', () => {
14
+ const bucket = new TokenBucket();
15
+ expect(bucket.availableTokens).toBe(DEFAULT_RATE_LIMIT_CONFIG.maxTokens);
16
+ });
17
+ it('accepts custom config', () => {
18
+ const bucket = new TokenBucket({ maxTokens: 10, refillRate: 5 });
19
+ expect(bucket.availableTokens).toBe(10);
20
+ });
21
+ it('allows partial config overrides', () => {
22
+ const bucket = new TokenBucket({ maxTokens: 20 });
23
+ expect(bucket.availableTokens).toBe(20);
24
+ });
25
+ // ========================================================================
26
+ // Token acquisition
27
+ // ========================================================================
28
+ it('resolves immediately when tokens are available', async () => {
29
+ const bucket = new TokenBucket({ maxTokens: 5, refillRate: 1 });
30
+ await bucket.acquire();
31
+ expect(bucket.availableTokens).toBe(4);
32
+ });
33
+ it('depletes tokens with multiple acquires', async () => {
34
+ const bucket = new TokenBucket({ maxTokens: 3, refillRate: 1 });
35
+ await bucket.acquire();
36
+ await bucket.acquire();
37
+ await bucket.acquire();
38
+ expect(bucket.availableTokens).toBe(0);
39
+ });
40
+ // ========================================================================
41
+ // Waiting when depleted
42
+ // ========================================================================
43
+ it('queues callers when tokens are exhausted', async () => {
44
+ const bucket = new TokenBucket({ maxTokens: 1, refillRate: 1 });
45
+ // Use up the only token
46
+ await bucket.acquire();
47
+ expect(bucket.availableTokens).toBe(0);
48
+ // This should not resolve immediately
49
+ let resolved = false;
50
+ const promise = bucket.acquire().then(() => {
51
+ resolved = true;
52
+ });
53
+ // Give microtasks a chance to run
54
+ await vi.advanceTimersByTimeAsync(0);
55
+ expect(resolved).toBe(false);
56
+ expect(bucket.pendingCount).toBe(1);
57
+ // Advance time so a token refills (1 token/sec => 1000ms for 1 token)
58
+ await vi.advanceTimersByTimeAsync(1000);
59
+ await promise;
60
+ expect(resolved).toBe(true);
61
+ expect(bucket.pendingCount).toBe(0);
62
+ });
63
+ it('drains multiple waiters as tokens refill', async () => {
64
+ const bucket = new TokenBucket({ maxTokens: 1, refillRate: 2 }); // 2 tokens/sec
65
+ await bucket.acquire();
66
+ const results = [];
67
+ const p1 = bucket.acquire().then(() => results.push(1));
68
+ const p2 = bucket.acquire().then(() => results.push(2));
69
+ expect(bucket.pendingCount).toBe(2);
70
+ // At 2 tokens/sec, each token takes 500ms
71
+ // First timer fires at 500ms, drains waiter 1, schedules next
72
+ await vi.advanceTimersByTimeAsync(500);
73
+ await Promise.resolve(); // let microtasks run
74
+ expect(results).toEqual([1]);
75
+ // Second timer fires at 1000ms total
76
+ await vi.advanceTimersByTimeAsync(500);
77
+ await Promise.resolve();
78
+ expect(results).toEqual([1, 2]);
79
+ await Promise.all([p1, p2]);
80
+ expect(bucket.pendingCount).toBe(0);
81
+ });
82
+ // ========================================================================
83
+ // Refill behavior
84
+ // ========================================================================
85
+ it('refills tokens over time', async () => {
86
+ const bucket = new TokenBucket({ maxTokens: 10, refillRate: 5 });
87
+ // Drain 5 tokens
88
+ for (let i = 0; i < 5; i++) {
89
+ await bucket.acquire();
90
+ }
91
+ expect(bucket.availableTokens).toBe(5);
92
+ // Advance 1 second => 5 new tokens refilled
93
+ vi.advanceTimersByTime(1000);
94
+ expect(bucket.availableTokens).toBe(10);
95
+ });
96
+ it('does not exceed maxTokens on refill', async () => {
97
+ const bucket = new TokenBucket({ maxTokens: 10, refillRate: 100 });
98
+ // Even after a long time, tokens should not exceed max
99
+ vi.advanceTimersByTime(10_000);
100
+ expect(bucket.availableTokens).toBe(10);
101
+ });
102
+ // ========================================================================
103
+ // penalize
104
+ // ========================================================================
105
+ it('penalize drains tokens to 0', () => {
106
+ const bucket = new TokenBucket({ maxTokens: 10, refillRate: 5 });
107
+ expect(bucket.availableTokens).toBe(10);
108
+ bucket.penalize(5);
109
+ expect(bucket.availableTokens).toBe(0);
110
+ });
111
+ it('penalize freezes token generation for the penalty period', () => {
112
+ const bucket = new TokenBucket({ maxTokens: 10, refillRate: 10 });
113
+ bucket.penalize(3); // 3 second penalty
114
+ // After 2 seconds (still within penalty), no tokens should be available
115
+ vi.advanceTimersByTime(2000);
116
+ expect(bucket.availableTokens).toBe(0);
117
+ // After 3 seconds total (penalty expired), refill should resume
118
+ vi.advanceTimersByTime(1000);
119
+ // Now tokens start refilling from 0 at 10/sec, but elapsed since penalty end is ~0
120
+ expect(bucket.availableTokens).toBe(0);
121
+ // After 4 seconds total (1 second of refill after penalty), 10 tokens
122
+ vi.advanceTimersByTime(1000);
123
+ expect(bucket.availableTokens).toBe(10);
124
+ });
125
+ // ========================================================================
126
+ // DEFAULT_RATE_LIMIT_CONFIG
127
+ // ========================================================================
128
+ it('exports sensible defaults', () => {
129
+ expect(DEFAULT_RATE_LIMIT_CONFIG.maxTokens).toBe(80);
130
+ expect(DEFAULT_RATE_LIMIT_CONFIG.refillRate).toBe(1.5);
131
+ });
132
+ });
133
+ // ===========================================================================
134
+ // extractRetryAfterMs
135
+ // ===========================================================================
136
+ describe('extractRetryAfterMs', () => {
137
+ it('returns null for non-object errors', () => {
138
+ expect(extractRetryAfterMs(null)).toBeNull();
139
+ expect(extractRetryAfterMs(undefined)).toBeNull();
140
+ expect(extractRetryAfterMs('string')).toBeNull();
141
+ expect(extractRetryAfterMs(42)).toBeNull();
142
+ });
143
+ it('returns null for non-429 errors', () => {
144
+ expect(extractRetryAfterMs({ status: 500 })).toBeNull();
145
+ expect(extractRetryAfterMs({ statusCode: 400 })).toBeNull();
146
+ expect(extractRetryAfterMs({ response: { status: 200 } })).toBeNull();
147
+ });
148
+ it('returns 60s default when 429 but no Retry-After header', () => {
149
+ expect(extractRetryAfterMs({ status: 429 })).toBe(60_000);
150
+ });
151
+ it('parses Retry-After from response.headers plain object', () => {
152
+ const error = {
153
+ status: 429,
154
+ response: {
155
+ status: 429,
156
+ headers: { 'retry-after': '30' },
157
+ },
158
+ };
159
+ expect(extractRetryAfterMs(error)).toBe(30_000);
160
+ });
161
+ it('parses Retry-After from response.headers.get() (fetch-style)', () => {
162
+ const headers = new Map([['retry-after', '45']]);
163
+ const error = {
164
+ status: 429,
165
+ response: {
166
+ status: 429,
167
+ headers: {
168
+ get: (name) => headers.get(name) ?? null,
169
+ },
170
+ },
171
+ };
172
+ expect(extractRetryAfterMs(error)).toBe(45_000);
173
+ });
174
+ it('parses Retry-After from error.headers', () => {
175
+ const error = {
176
+ status: 429,
177
+ headers: { 'retry-after': '10' },
178
+ };
179
+ expect(extractRetryAfterMs(error)).toBe(10_000);
180
+ });
181
+ it('detects 429 from response.status when top-level status is missing', () => {
182
+ const error = {
183
+ response: {
184
+ status: 429,
185
+ headers: { 'retry-after': '20' },
186
+ },
187
+ };
188
+ expect(extractRetryAfterMs(error)).toBe(20_000);
189
+ });
190
+ it('detects 429 from statusCode property', () => {
191
+ const error = {
192
+ statusCode: 429,
193
+ headers: { 'retry-after': '15' },
194
+ };
195
+ expect(extractRetryAfterMs(error)).toBe(15_000);
196
+ });
197
+ it('falls back to 60s for invalid Retry-After value', () => {
198
+ const error = {
199
+ status: 429,
200
+ response: {
201
+ status: 429,
202
+ headers: { 'retry-after': 'not-a-number' },
203
+ },
204
+ };
205
+ expect(extractRetryAfterMs(error)).toBe(60_000);
206
+ });
207
+ it('falls back to 60s for zero Retry-After', () => {
208
+ const error = {
209
+ status: 429,
210
+ response: {
211
+ status: 429,
212
+ headers: { 'retry-after': '0' },
213
+ },
214
+ };
215
+ expect(extractRetryAfterMs(error)).toBe(60_000);
216
+ });
217
+ });
@@ -31,9 +31,25 @@ export interface WithRetryOptions {
31
31
  config?: RetryConfig;
32
32
  onRetry?: RetryCallback;
33
33
  shouldRetry?: (error: unknown) => boolean;
34
+ /**
35
+ * Optional callback to extract a rate-limit delay (in ms) from an error.
36
+ * When provided and returns a positive number, that delay is used instead
37
+ * of the standard exponential backoff for that retry attempt.
38
+ */
39
+ getRetryAfterMs?: (error: unknown) => number | null;
40
+ /**
41
+ * Optional callback invoked when a rate limit is detected (getRetryAfterMs
42
+ * returned a value). Use this to penalize a shared token bucket so other
43
+ * concurrent callers also back off.
44
+ */
45
+ onRateLimited?: (retryAfterMs: number) => void;
34
46
  }
35
47
  /**
36
- * Execute an async function with exponential backoff retry logic
48
+ * Execute an async function with exponential backoff retry logic.
49
+ *
50
+ * When `getRetryAfterMs` is provided and returns a positive delay for an
51
+ * error, that delay is used instead of exponential backoff. This allows
52
+ * honoring HTTP 429 Retry-After headers from upstream APIs.
37
53
  */
38
54
  export declare function withRetry<T>(fn: () => Promise<T>, options?: WithRetryOptions): Promise<T>;
39
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,WAAW,CAMtD,CAAA;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC5B,MAAM,CAIR;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;AAE3D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CAC1C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CAsCZ;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,GAAE,gBAAqB,IACrD,CAAC,EAChB,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,gBAAgB,KACzB,OAAO,CAAC,CAAC,CAAC,CAUd"}
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,WAAW,CAMtD,CAAA;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC5B,MAAM,CAIR;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;AAE3D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;IACzC;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAA;IACnD;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/C;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,CAAC,CAAC,CA4CZ;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,GAAE,gBAAqB,IACrD,CAAC,EAChB,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,UAAU,gBAAgB,KACzB,OAAO,CAAC,CAAC,CAAC,CAUd"}
package/dist/src/retry.js CHANGED
@@ -23,7 +23,11 @@ export function calculateDelay(attempt, config) {
23
23
  return Math.min(delay, config.maxDelayMs);
24
24
  }
25
25
  /**
26
- * Execute an async function with exponential backoff retry logic
26
+ * Execute an async function with exponential backoff retry logic.
27
+ *
28
+ * When `getRetryAfterMs` is provided and returns a positive delay for an
29
+ * error, that delay is used instead of exponential backoff. This allows
30
+ * honoring HTTP 429 Retry-After headers from upstream APIs.
27
31
  */
28
32
  export async function withRetry(fn, options = {}) {
29
33
  const config = {
@@ -42,7 +46,12 @@ export async function withRetry(fn, options = {}) {
42
46
  if (attempt === config.maxRetries || !shouldRetry(error)) {
43
47
  throw lastError;
44
48
  }
45
- const delay = calculateDelay(attempt, config);
49
+ // Check for rate-limit-specific delay (Retry-After)
50
+ const retryAfterMs = options.getRetryAfterMs?.(error) ?? null;
51
+ const delay = retryAfterMs ?? calculateDelay(attempt, config);
52
+ if (retryAfterMs !== null && options.onRateLimited) {
53
+ options.onRateLimited(retryAfterMs);
54
+ }
46
55
  if (options.onRetry) {
47
56
  options.onRetry({
48
57
  attempt,
@@ -152,6 +152,8 @@ export interface LinearAgentClientConfig {
152
152
  apiKey: string;
153
153
  baseUrl?: string;
154
154
  retry?: RetryConfig;
155
+ /** Token bucket rate limiter configuration. Applied to all API calls. */
156
+ rateLimit?: Partial<import('./rate-limiter.js').TokenBucketConfig>;
155
157
  }
156
158
  /**
157
159
  * Retry configuration with exponential backoff
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,QAAQ,GACR,OAAO,GACP,eAAe,GACf,UAAU,CAAA;AAEd;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,QAAQ,GACR,UAAU,GACV,aAAa,GACb,OAAO,GACP,QAAQ,CAAA;AAEZ;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEzE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,UAAU,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,QAAQ,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,MAAM,MAAM,2BAA2B,GACnC,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,GACvB,0BAA0B,GAC1B,oBAAoB,GACpB,qBAAqB,CAAA;AAEzB;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,2BAA2B,CAAA;IACpC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,mBAAmB,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,iBAAiB,CAAA;IACvB,OAAO,EAAE,oBAAoB,CAAA;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B,SAAS,GACT,YAAY,GACZ,WAAW,GACX,UAAU,CAAA;AAEd;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,CAAA;AAElF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,iCAAiC;IACjC,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,kBAAkB,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,CAAC,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,yEAAyE;IACzE,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,mBAAmB,EAAE,iBAAiB,CAAC,CAAA;CACnE;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;CAChC;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,SAAS,GACT,UAAU,GACV,WAAW,GACX,UAAU,GACV,UAAU,GACV,UAAU,CAAA;AAEd;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,kBAAkB,GAClB,aAAa,GACb,UAAU,GACV,IAAI,GACJ,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,iBAAiB,GACjB,yBAAyB,CAAA;AAE7B;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAO9D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,gDAAiD,CAAA;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAA;AAE7D;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAWrF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAWxF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAWpF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAWtE,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAOnE,CAAA;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE1E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,aAAa,EACvB,WAAW,EAAE,MAAM,GAClB,wBAAwB,CAW1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,YAAY,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAA;IACb,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,oDAAoD;IACpD,YAAY,CAAC,EAAE,uBAAuB,EAAE,CAAA;IACxC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,cAAc,EAAE,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,YAAY,CAAC,EAAE,uBAAuB,EAAE,CAAA;IACxC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAA;IAChB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;AAElE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,iBAAiB,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,MAAM,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACxD;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAA;QACV,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,EAAE,CAAA;QAChB,GAAG,EAAE,MAAM,CAAA;KACZ,CAAA;IACD,sDAAsD;IACtD,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,uDAAuD;IACvD,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,iBAAiB,EAAE,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,SAAS,EAAE,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,gBAAgB,EAAE,iBAAiB,EAAE,CAAA;CACtC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supaku/agentfactory-linear",
3
- "version": "0.7.13",
3
+ "version": "0.7.14",
4
4
  "type": "module",
5
5
  "description": "Linear issue tracker integration for AgentFactory — status transitions, agent sessions, work routing",
6
6
  "author": "Supaku (https://supaku.com)",