@framers/agentos-ext-github 0.2.2 → 0.3.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 (39) hide show
  1. package/README.md +43 -11
  2. package/dist/__tests__/app.test.js +155 -1
  3. package/dist/__tests__/app.test.js.map +1 -1
  4. package/dist/__tests__/documents.test.js +28 -1
  5. package/dist/__tests__/documents.test.js.map +1 -1
  6. package/dist/__tests__/events.test.d.ts +8 -0
  7. package/dist/__tests__/events.test.d.ts.map +1 -0
  8. package/dist/__tests__/events.test.js +304 -0
  9. package/dist/__tests__/events.test.js.map +1 -0
  10. package/dist/app/events.d.ts +142 -0
  11. package/dist/app/events.d.ts.map +1 -0
  12. package/dist/app/events.js +182 -0
  13. package/dist/app/events.js.map +1 -0
  14. package/dist/app/http.d.ts +82 -13
  15. package/dist/app/http.d.ts.map +1 -1
  16. package/dist/app/http.js +151 -23
  17. package/dist/app/http.js.map +1 -1
  18. package/dist/app/installations.d.ts +8 -6
  19. package/dist/app/installations.d.ts.map +1 -1
  20. package/dist/app/installations.js +21 -15
  21. package/dist/app/installations.js.map +1 -1
  22. package/dist/app/reconcile.d.ts +1 -1
  23. package/dist/app/reconcile.d.ts.map +1 -1
  24. package/dist/app/reconcile.js +8 -7
  25. package/dist/app/reconcile.js.map +1 -1
  26. package/dist/app/userFlow.d.ts +10 -3
  27. package/dist/app/userFlow.d.ts.map +1 -1
  28. package/dist/app/userFlow.js +43 -4
  29. package/dist/app/userFlow.js.map +1 -1
  30. package/dist/documents/GitHubDocumentsConnector.d.ts +7 -4
  31. package/dist/documents/GitHubDocumentsConnector.d.ts.map +1 -1
  32. package/dist/documents/GitHubDocumentsConnector.js +39 -18
  33. package/dist/documents/GitHubDocumentsConnector.js.map +1 -1
  34. package/dist/documents/index.d.ts +11 -6
  35. package/dist/documents/index.d.ts.map +1 -1
  36. package/dist/documents/index.js +11 -6
  37. package/dist/documents/index.js.map +1 -1
  38. package/manifest.json +4 -2
  39. package/package.json +5 -4
@@ -0,0 +1,182 @@
1
+ /**
2
+ * @fileoverview A GitHub App's webhook deliveries read as typed events. A delivery's size is checked first, then its
3
+ * `X-Hub-Signature-256` on the raw body, then its `X-GitHub-Delivery` id, and only then is its body parsed; each event
4
+ * an app reading its installations' repositories acts on (a push, an installation's actions, the repositories it
5
+ * reaches, its account renamed, a ping) is answered with the fields that reading needs, and every other delivery as
6
+ * `other`. No text of a payload reaches an error, and nothing of a payload but those fields leaves the reader.
7
+ * Payloads as https://docs.github.com/en/webhooks/webhook-events-and-payloads documents them.
8
+ * @module agentos-ext-github/app/events
9
+ */
10
+ import { readCapped, ReadCapExceeded } from '@framers/agentos-ext-account-sources';
11
+ import { arrayOf, numberIn, objectOf, stringIn } from './http.js';
12
+ import { accountOf } from './installations.js';
13
+ import { verifyWebhookSignature } from './webhook.js';
14
+ /** The largest delivery read by default: 25 MiB (26,214,400 bytes). GitHub delivers no payload past 25 MB. */
15
+ export const DEFAULT_MAX_DELIVERY_BYTES = 26_214_400;
16
+ /** A delivery the reader refused. The message names the reason alone, never the body or a header's value. */
17
+ export class WebhookDeliveryRefused extends Error {
18
+ reason;
19
+ /** @param reason Why the delivery was refused. */
20
+ constructor(reason) {
21
+ super(`the webhook delivery was refused: ${reason}`);
22
+ this.reason = reason;
23
+ this.name = 'WebhookDeliveryRefused';
24
+ }
25
+ }
26
+ const INSTALLATION_ACTIONS = new Set(['created', 'deleted', 'suspend', 'unsuspend', 'new_permissions_accepted']);
27
+ /** A delivery id: a GUID's hexadecimal digits and hyphens. */
28
+ const DELIVERY_ID = /^[0-9A-Fa-f-]{8,64}$/;
29
+ /** An event name as GitHub sends it. */
30
+ const EVENT_NAME = /^[a-z][a-z0-9_]{0,63}$/;
31
+ /** A commit's SHA-1 or SHA-256 name. */
32
+ const COMMIT = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
33
+ function booleanIn(fields, key, what) {
34
+ const value = fields[key];
35
+ if (typeof value !== 'boolean')
36
+ throw new Error(`GitHub sent ${what} without a boolean ${key}`);
37
+ return value;
38
+ }
39
+ function commitIn(fields, key) {
40
+ const value = stringIn(fields, key, 'a push');
41
+ if (!COMMIT.test(value))
42
+ throw new Error(`GitHub sent a push whose ${key} is not a commit`);
43
+ return value;
44
+ }
45
+ function installationIdOf(payload) {
46
+ return numberIn(objectOf(payload.installation, "an event's installation"), 'id', "an event's installation");
47
+ }
48
+ function repositoriesIn(value, what) {
49
+ return arrayOf(value, what).map((entry) => {
50
+ const repository = objectOf(entry, what);
51
+ return { id: numberIn(repository, 'id', what), fullName: stringIn(repository, 'full_name', what) };
52
+ });
53
+ }
54
+ function pushOf(payload) {
55
+ const repository = objectOf(payload.repository, "a push's repository");
56
+ const defaultBranch = stringIn(repository, 'default_branch', "a push's repository");
57
+ const ref = stringIn(payload, 'ref', 'a push');
58
+ const deleted = booleanIn(payload, 'deleted', 'a push');
59
+ return {
60
+ kind: 'push',
61
+ installationId: installationIdOf(payload),
62
+ repository: { id: numberIn(repository, 'id', "a push's repository"), fullName: stringIn(repository, 'full_name', "a push's repository"), defaultBranch },
63
+ ref,
64
+ before: commitIn(payload, 'before'),
65
+ after: commitIn(payload, 'after'),
66
+ created: booleanIn(payload, 'created', 'a push'),
67
+ deleted,
68
+ forced: booleanIn(payload, 'forced', 'a push'),
69
+ toDefaultBranch: !deleted && ref === `refs/heads/${defaultBranch}`,
70
+ };
71
+ }
72
+ function installationOf(payload) {
73
+ const action = stringIn(payload, 'action', 'an installation event');
74
+ if (!INSTALLATION_ACTIONS.has(action))
75
+ return { kind: 'other' };
76
+ const installation = objectOf(payload.installation, "an event's installation");
77
+ return {
78
+ kind: 'installation',
79
+ action: action,
80
+ installationId: numberIn(installation, 'id', "an event's installation"),
81
+ account: installation.account === null || installation.account === undefined ? null : accountOf(installation.account),
82
+ repositories: payload.repositories === undefined || payload.repositories === null ? [] : repositoriesIn(payload.repositories, 'an installed repository'),
83
+ };
84
+ }
85
+ function installationRepositoriesOf(payload) {
86
+ const action = stringIn(payload, 'action', 'an installation_repositories event');
87
+ if (action !== 'added' && action !== 'removed')
88
+ return { kind: 'other' };
89
+ const selection = stringIn(payload, 'repository_selection', 'an installation_repositories event');
90
+ if (selection !== 'all' && selection !== 'selected')
91
+ return { kind: 'other' };
92
+ return {
93
+ kind: 'installation_repositories',
94
+ action,
95
+ installationId: installationIdOf(payload),
96
+ selection,
97
+ added: repositoriesIn(payload.repositories_added, 'an added repository'),
98
+ removed: repositoriesIn(payload.repositories_removed, 'a removed repository'),
99
+ };
100
+ }
101
+ function installationTargetOf(payload) {
102
+ if (stringIn(payload, 'action', 'an installation_target event') !== 'renamed')
103
+ return { kind: 'other' };
104
+ return { kind: 'installation_target', action: 'renamed', installationId: installationIdOf(payload), account: accountOf(payload.account) };
105
+ }
106
+ /** The event a payload holds, by the delivery's event name; `other` for a name or a payload it does not read. */
107
+ function eventOf(name, payload) {
108
+ try {
109
+ if (name === 'push')
110
+ return pushOf(payload);
111
+ if (name === 'installation')
112
+ return installationOf(payload);
113
+ if (name === 'installation_repositories')
114
+ return installationRepositoriesOf(payload);
115
+ if (name === 'installation_target')
116
+ return installationTargetOf(payload);
117
+ if (name === 'ping')
118
+ return { kind: 'ping' };
119
+ }
120
+ catch {
121
+ // A payload without a field the event needs is an event this reader does not read; its text is not kept.
122
+ }
123
+ return { kind: 'other' };
124
+ }
125
+ /**
126
+ * Reads a delivery already received whole: refuses it past `maxBytes` (`too_large`), when its signature under
127
+ * `secret` does not hold (`signature`), when it has no usable `X-GitHub-Delivery` (`delivery`), or when its signed
128
+ * body is not a JSON object in UTF-8 (`body`), each with {@link WebhookDeliveryRefused}; answers its id, its event's
129
+ * name and its typed event otherwise. A receiver answers GitHub at once and applies each delivery id once: GitHub
130
+ * expects a 2xx within 10 seconds, and a redelivery carries the id of the delivery it repeats.
131
+ *
132
+ * @param input The delivery's headers and its raw body exactly as received.
133
+ * @param secret The webhook's secret, never empty.
134
+ * @param options The largest body read.
135
+ * @throws TypeError when the secret is empty or missing: anyone can sign a delivery under an empty key.
136
+ */
137
+ export function readWebhookDelivery(input, secret, options = {}) {
138
+ if (input.body.byteLength > (options.maxBytes ?? DEFAULT_MAX_DELIVERY_BYTES))
139
+ throw new WebhookDeliveryRefused('too_large');
140
+ if (!verifyWebhookSignature(input.body, input.headers.get('x-hub-signature-256'), secret))
141
+ throw new WebhookDeliveryRefused('signature');
142
+ const id = (input.headers.get('x-github-delivery') ?? '').trim();
143
+ if (!DELIVERY_ID.test(id))
144
+ throw new WebhookDeliveryRefused('delivery');
145
+ let payload;
146
+ try {
147
+ payload = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(input.body));
148
+ }
149
+ catch {
150
+ // The parser's message would quote the body.
151
+ throw new WebhookDeliveryRefused('body');
152
+ }
153
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload))
154
+ throw new WebhookDeliveryRefused('body');
155
+ const header = (input.headers.get('x-github-event') ?? '').trim();
156
+ const name = EVENT_NAME.test(header) ? header : '';
157
+ return { id, name, event: eventOf(name, payload) };
158
+ }
159
+ /**
160
+ * Reads a delivery from a request: its body read as it streams and refused at `maxBytes` (`too_large`), before any
161
+ * check that needs it whole, the stream then cancelled unread; a `Content-Length` past the cap is refused before a byte
162
+ * is read. Then as {@link readWebhookDelivery}.
163
+ *
164
+ * @param request The request, or its headers and body stream.
165
+ * @param secret The webhook's secret, never empty.
166
+ * @param options The largest body read.
167
+ */
168
+ export async function readWebhookRequest(request, secret, options = {}) {
169
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_DELIVERY_BYTES;
170
+ const declared = request.headers.get('content-length');
171
+ let body;
172
+ try {
173
+ body = await readCapped(new Response(request.body, { headers: declared === null ? {} : { 'content-length': declared } }), maxBytes);
174
+ }
175
+ catch (error) {
176
+ if (error instanceof ReadCapExceeded)
177
+ throw new WebhookDeliveryRefused('too_large');
178
+ throw error;
179
+ }
180
+ return readWebhookDelivery({ headers: request.headers, body }, secret, { maxBytes });
181
+ }
182
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/app/events.ts"],"names":[],"mappings":"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"}
@@ -1,19 +1,32 @@
1
1
  /**
2
- * @fileoverview GitHub's REST API over an injected `fetch`: one call with the headers every call carries, the pages
3
- * of a listing followed through its `Link` header, and the small readers that turn an answer into typed fields. An
4
- * answer outside 2xx throws {@link GitHubApiError}, whose message names the status and nothing of the answer's body.
2
+ * @fileoverview GitHub's REST API over an injected `fetch`: one call under a deadline with the headers every call
3
+ * carries, the pages of a listing followed through its `Link` header, and the small readers that turn an answer into
4
+ * typed fields. An answer outside 2xx throws {@link GitHubApiError}, whose message names the status and nothing of
5
+ * the answer's body and which says whether GitHub refused for a rate limit and when to try again; a call that gets no
6
+ * answer throws {@link GitHubNoAnswer}. An answer's body is read under a byte cap.
5
7
  * @module agentos-ext-github/app/http
6
8
  */
7
9
  /** The REST API version every call pins (`X-GitHub-Api-Version`). */
8
10
  export declare const GITHUB_API_VERSION = "2026-03-10";
9
11
  /** GitHub's REST API. */
10
12
  export declare const GITHUB_API_BASE = "https://api.github.com";
11
- /** Where calls go: the fetch they are made with and the API's base address; `fetch` and {@link GITHUB_API_BASE} when absent. */
13
+ /**
14
+ * How long one call may take when its caller sets no other deadline, its answer's body included: 30 seconds. GitHub
15
+ * ends a request it takes more than 10 seconds to process.
16
+ */
17
+ export declare const GITHUB_TIMEOUT_MS = 30000;
18
+ /** The largest JSON answer read: 16 MiB, above the 7 MB at which GitHub cuts a recursive tree. */
19
+ export declare const GITHUB_MAX_ANSWER_BYTES = 16777216;
20
+ /** Where calls go: the fetch they are made with, the API's base address and each call's deadline. */
12
21
  export interface GitHubApiOptions {
22
+ /** The fetch calls are made with; the global `fetch` when absent. */
13
23
  fetchImpl?: typeof fetch;
24
+ /** GitHub's REST API, or a stand-in's address in tests; {@link GITHUB_API_BASE} when absent. */
14
25
  apiBase?: string;
26
+ /** How long each call may take, its answer's body included; {@link GITHUB_TIMEOUT_MS} when absent. */
27
+ timeoutMs?: number;
15
28
  }
16
- /** One call: the bearer it carries, and its method, JSON body, media type and abort signal when not the defaults. */
29
+ /** One call: the bearer it carries, and its method, JSON body, media type, abort signal and deadline when not the defaults. */
17
30
  export interface GitHubRequest {
18
31
  token: string;
19
32
  method?: string;
@@ -22,30 +35,86 @@ export interface GitHubRequest {
22
35
  /** The `Accept` media type; `application/vnd.github+json` when absent. */
23
36
  accept?: string;
24
37
  signal?: AbortSignal;
38
+ /** How long the call may take, its answer's body included; {@link GITHUB_TIMEOUT_MS} when absent. */
39
+ timeoutMs?: number;
40
+ }
41
+ /** Whether a refusal was a rate limit, and when the call may be made again. */
42
+ export interface GitHubRefusalTiming {
43
+ /** True for a rate limit: a 429, or a 403 with `retry-after`, `x-ratelimit-remaining: 0` or a body naming a rate limit. */
44
+ rateLimited: boolean;
45
+ /** Epoch milliseconds before which the call should not be made again, or null when GitHub asked for no wait. */
46
+ retryAt: number | null;
25
47
  }
26
- /** GitHub answered a status outside 2xx. The message holds the status alone, never the answer's body. */
48
+ /**
49
+ * GitHub answered a status outside 2xx. The message holds the status alone, never the answer's body. `rateLimited`
50
+ * and `retryAt` follow GitHub's rules for a refused call
51
+ * (https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#exceeding-the-rate-limit):
52
+ * `retry-after` first, then `x-ratelimit-reset` when `x-ratelimit-remaining` is 0, else a minute for a rate limit
53
+ * that names no time.
54
+ */
27
55
  export declare class GitHubApiError extends Error {
28
56
  readonly status: number;
29
- /** @param status The HTTP status GitHub answered. */
30
- constructor(status: number);
57
+ /** True when GitHub refused the call for a rate limit. */
58
+ readonly rateLimited: boolean;
59
+ /** Epoch milliseconds before which the call should not be made again, or null when GitHub asked for no wait. */
60
+ readonly retryAt: number | null;
61
+ /**
62
+ * @param status The HTTP status GitHub answered.
63
+ * @param timing Whether it was a rate limit and when to try again; neither when absent.
64
+ */
65
+ constructor(status: number, timing?: Partial<GitHubRefusalTiming>);
66
+ }
67
+ /** Why a call got no answer: its deadline passed, its caller cancelled it, or the connection failed. */
68
+ export type GitHubNoAnswerReason = 'timeout' | 'cancelled' | 'network';
69
+ /** A call that got no answer from GitHub, or whose answer's body stopped before its end. Names no address or cause. */
70
+ export declare class GitHubNoAnswer extends Error {
71
+ readonly reason: GitHubNoAnswerReason;
72
+ /** @param reason Why the call got no answer. */
73
+ constructor(reason: GitHubNoAnswerReason);
31
74
  }
32
75
  /** The headers of a call: the bearer, the media type, the pinned API version and the pack's user agent. */
33
76
  export declare function githubHeaders(token: string, accept?: string): Record<string, string>;
34
77
  /** Releases an answer's body unread. */
35
78
  export declare function discardBody(response: Response): Promise<void>;
36
- /** Sends one call; an answer outside 2xx is discarded and thrown as {@link GitHubApiError}. */
37
- export declare function githubSend(fetchImpl: typeof fetch, url: string, request: GitHubRequest): Promise<Response>;
79
+ /** A call's signal (the caller's, ended at the deadline) and the failure its transport errors read as. */
80
+ export interface CallDeadline {
81
+ signal: AbortSignal;
82
+ /** The failure a fetch or a body read that threw reads as: the caller's cancel, the deadline, or the network. */
83
+ noAnswer(): GitHubNoAnswer;
84
+ }
85
+ /**
86
+ * The deadline of one call: its own timeout joined with the caller's signal, so the call and the reading of its body
87
+ * end at whichever comes first.
88
+ *
89
+ * @param signal The caller's signal, if any.
90
+ * @param timeoutMs How long the call may take; {@link GITHUB_TIMEOUT_MS} when absent.
91
+ */
92
+ export declare function callDeadline(signal: AbortSignal | undefined, timeoutMs?: number): CallDeadline;
93
+ /** A 2xx answer, and its body read under the call's deadline. */
94
+ export interface GitHubAnswer {
95
+ readonly response: Response;
96
+ /**
97
+ * The answer's body, read as it streams and cut at `maxBytes` ({@link ReadCapExceeded} past it, the rest cancelled
98
+ * unread). A body that stops before its end, at the deadline or the caller's cancel, throws {@link GitHubNoAnswer}.
99
+ */
100
+ bytes(maxBytes: number): Promise<Uint8Array>;
101
+ }
102
+ /**
103
+ * Sends one call under its deadline and the caller's signal. An answer outside 2xx is read only as far as telling a
104
+ * rate limit needs and thrown as {@link GitHubApiError}; a call that gets no answer throws {@link GitHubNoAnswer}.
105
+ */
106
+ export declare function githubSend(fetchImpl: typeof fetch, url: string, request: GitHubRequest): Promise<GitHubAnswer>;
38
107
  /**
39
108
  * One call to `url` (an address under the API's base), answering its JSON, or `undefined` for an answer without a
40
109
  * body. Sends `Authorization: Bearer <token>`, `Accept: application/vnd.github+json` unless told otherwise,
41
- * `X-GitHub-Api-Version` and `User-Agent`.
110
+ * `X-GitHub-Api-Version` and `User-Agent`, and ends at its deadline.
42
111
  */
43
112
  export declare function githubFetch(fetchImpl: typeof fetch, url: string, request: GitHubRequest): Promise<unknown>;
44
113
  /** The address a `Link` header names as `rel="next"`, or null. */
45
114
  export declare function nextPage(link: string | null): string | null;
46
115
  /**
47
- * Each page of a listing as its JSON, following `Link: <...>; rel="next"`. A next page on another origin than the
48
- * first is refused, so the bearer never leaves the API it was sent to.
116
+ * Each page of a listing as its JSON, following `Link: <...>; rel="next"`, each page under a deadline of its own. A
117
+ * next page on another origin than the first is refused, so the bearer never leaves the API it was sent to.
49
118
  */
50
119
  export declare function githubPages(fetchImpl: typeof fetch, url: string, request: GitHubRequest): AsyncGenerator<unknown, void, undefined>;
51
120
  /** Every item of a listing across its pages; `items` takes a page's JSON and answers its items. */
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/app/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,yBAAyB;AACzB,eAAO,MAAM,eAAe,2BAA2B,CAAC;AAExD,gIAAgI;AAChI,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qHAAqH;AACrH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,yGAAyG;AACzG,qBAAa,cAAe,SAAQ,KAAK;IAE3B,QAAQ,CAAC,MAAM,EAAE,MAAM;IADnC,qDAAqD;gBAChC,MAAM,EAAE,MAAM;CAIpC;AAED,2GAA2G;AAC3G,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAgC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO3G;AAED,wCAAwC;AACxC,wBAAsB,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnE;AAED,+FAA+F;AAC/F,wBAAsB,UAAU,CAAC,SAAS,EAAE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAShH;AAcD;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhH;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED;;;GAGG;AACH,wBAAuB,WAAW,CAAC,SAAS,EAAE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAUzI;AAED,mGAAmG;AACnG,wBAAsB,KAAK,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAIjJ;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAG9E;AAED,0EAA0E;AAC1E,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAG/D;AAED,4CAA4C;AAC5C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3F;AAED,4CAA4C;AAC5C,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3F"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/app/http.ts"],"names":[],"mappings":"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"}
package/dist/app/http.js CHANGED
@@ -1,21 +1,60 @@
1
1
  /**
2
- * @fileoverview GitHub's REST API over an injected `fetch`: one call with the headers every call carries, the pages
3
- * of a listing followed through its `Link` header, and the small readers that turn an answer into typed fields. An
4
- * answer outside 2xx throws {@link GitHubApiError}, whose message names the status and nothing of the answer's body.
2
+ * @fileoverview GitHub's REST API over an injected `fetch`: one call under a deadline with the headers every call
3
+ * carries, the pages of a listing followed through its `Link` header, and the small readers that turn an answer into
4
+ * typed fields. An answer outside 2xx throws {@link GitHubApiError}, whose message names the status and nothing of
5
+ * the answer's body and which says whether GitHub refused for a rate limit and when to try again; a call that gets no
6
+ * answer throws {@link GitHubNoAnswer}. An answer's body is read under a byte cap.
5
7
  * @module agentos-ext-github/app/http
6
8
  */
9
+ import { readCapped, ReadCapExceeded } from '@framers/agentos-ext-account-sources';
7
10
  /** The REST API version every call pins (`X-GitHub-Api-Version`). */
8
11
  export const GITHUB_API_VERSION = '2026-03-10';
9
12
  /** GitHub's REST API. */
10
13
  export const GITHUB_API_BASE = 'https://api.github.com';
11
- /** GitHub answered a status outside 2xx. The message holds the status alone, never the answer's body. */
14
+ /**
15
+ * How long one call may take when its caller sets no other deadline, its answer's body included: 30 seconds. GitHub
16
+ * ends a request it takes more than 10 seconds to process.
17
+ */
18
+ export const GITHUB_TIMEOUT_MS = 30_000;
19
+ /** The largest JSON answer read: 16 MiB, above the 7 MB at which GitHub cuts a recursive tree. */
20
+ export const GITHUB_MAX_ANSWER_BYTES = 16_777_216;
21
+ /** How much of a refused call's body is read to tell a rate limit from another 403. */
22
+ const REFUSAL_READ_BYTES = 16_384;
23
+ /** The wait after a rate limit that names no time: GitHub asks for at least a minute. */
24
+ const RATE_LIMIT_WAIT_MS = 60_000;
25
+ /**
26
+ * GitHub answered a status outside 2xx. The message holds the status alone, never the answer's body. `rateLimited`
27
+ * and `retryAt` follow GitHub's rules for a refused call
28
+ * (https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#exceeding-the-rate-limit):
29
+ * `retry-after` first, then `x-ratelimit-reset` when `x-ratelimit-remaining` is 0, else a minute for a rate limit
30
+ * that names no time.
31
+ */
12
32
  export class GitHubApiError extends Error {
13
33
  status;
14
- /** @param status The HTTP status GitHub answered. */
15
- constructor(status) {
34
+ /** True when GitHub refused the call for a rate limit. */
35
+ rateLimited;
36
+ /** Epoch milliseconds before which the call should not be made again, or null when GitHub asked for no wait. */
37
+ retryAt;
38
+ /**
39
+ * @param status The HTTP status GitHub answered.
40
+ * @param timing Whether it was a rate limit and when to try again; neither when absent.
41
+ */
42
+ constructor(status, timing = {}) {
16
43
  super(`GitHub answered ${status}`);
17
44
  this.status = status;
18
45
  this.name = 'GitHubApiError';
46
+ this.rateLimited = timing.rateLimited ?? false;
47
+ this.retryAt = timing.retryAt ?? null;
48
+ }
49
+ }
50
+ /** A call that got no answer from GitHub, or whose answer's body stopped before its end. Names no address or cause. */
51
+ export class GitHubNoAnswer extends Error {
52
+ reason;
53
+ /** @param reason Why the call got no answer. */
54
+ constructor(reason) {
55
+ super(`GitHub gave no answer: ${reason}`);
56
+ this.reason = reason;
57
+ this.name = 'GitHubNoAnswer';
19
58
  }
20
59
  }
21
60
  /** The headers of a call: the bearer, the media type, the pinned API version and the pack's user agent. */
@@ -36,25 +75,114 @@ export async function discardBody(response) {
36
75
  // The body is not needed; a failed cancel changes nothing for the caller.
37
76
  }
38
77
  }
39
- /** Sends one call; an answer outside 2xx is discarded and thrown as {@link GitHubApiError}. */
78
+ /**
79
+ * The deadline of one call: its own timeout joined with the caller's signal, so the call and the reading of its body
80
+ * end at whichever comes first.
81
+ *
82
+ * @param signal The caller's signal, if any.
83
+ * @param timeoutMs How long the call may take; {@link GITHUB_TIMEOUT_MS} when absent.
84
+ */
85
+ export function callDeadline(signal, timeoutMs = GITHUB_TIMEOUT_MS) {
86
+ const timeout = AbortSignal.timeout(timeoutMs);
87
+ return {
88
+ signal: signal === undefined ? timeout : AbortSignal.any([signal, timeout]),
89
+ noAnswer: () => new GitHubNoAnswer(signal?.aborted === true ? 'cancelled' : timeout.aborted ? 'timeout' : 'network'),
90
+ };
91
+ }
92
+ /** True when a refused call's body names a rate limit, read up to {@link REFUSAL_READ_BYTES}; the body is never kept. */
93
+ async function bodyNamesRateLimit(response) {
94
+ let fields;
95
+ try {
96
+ fields = JSON.parse(new TextDecoder().decode(await readCapped(response, REFUSAL_READ_BYTES)));
97
+ }
98
+ catch {
99
+ // An unreadable or oversized refusal is told by its status and headers alone.
100
+ return false;
101
+ }
102
+ if (typeof fields !== 'object' || fields === null || Array.isArray(fields))
103
+ return false;
104
+ const { message, documentation_url: documentation } = fields;
105
+ return (typeof message === 'string' && /rate limit/i.test(message)) || (typeof documentation === 'string' && /rate-limit/i.test(documentation));
106
+ }
107
+ /**
108
+ * The time a `retry-after` header gives, as delay-seconds or an HTTP date, in epoch milliseconds, or null when it
109
+ * gives none that can be read. The wait is kept whole, however long: GitHub asks that no call be made before it ends.
110
+ * A date already past answers `now`.
111
+ */
112
+ function retryAfterAt(header, now) {
113
+ const value = (header ?? '').trim();
114
+ if (/^\d+$/.test(value)) {
115
+ const at = now + Number(value) * 1000;
116
+ return Number.isSafeInteger(at) ? at : null;
117
+ }
118
+ const date = value === '' ? Number.NaN : Date.parse(value);
119
+ return Number.isFinite(date) ? Math.max(date, now) : null;
120
+ }
121
+ /** The refusal an answer outside 2xx reads as: its status, whether it was a rate limit, and when to try again. */
122
+ async function refusalOf(response) {
123
+ const now = Date.now();
124
+ const retryAfter = retryAfterAt(response.headers.get('retry-after'), now);
125
+ const exhausted = response.headers.get('x-ratelimit-remaining')?.trim() === '0';
126
+ const reset = (response.headers.get('x-ratelimit-reset') ?? '').trim();
127
+ let rateLimited = response.status === 429 || (response.status === 403 && (retryAfter !== null || exhausted));
128
+ if (!rateLimited && response.status === 403)
129
+ rateLimited = await bodyNamesRateLimit(response);
130
+ else
131
+ await discardBody(response);
132
+ let retryAt = retryAfter;
133
+ if (retryAt === null && rateLimited) {
134
+ retryAt = exhausted && /^\d+$/.test(reset) ? Math.max(Number(reset) * 1000, now + 1000) : now + RATE_LIMIT_WAIT_MS;
135
+ }
136
+ return new GitHubApiError(response.status, { rateLimited, retryAt });
137
+ }
138
+ /**
139
+ * Sends one call under its deadline and the caller's signal. An answer outside 2xx is read only as far as telling a
140
+ * rate limit needs and thrown as {@link GitHubApiError}; a call that gets no answer throws {@link GitHubNoAnswer}.
141
+ */
40
142
  export async function githubSend(fetchImpl, url, request) {
41
143
  const headers = githubHeaders(request.token, request.accept);
42
144
  if (request.body !== undefined)
43
145
  headers['content-type'] = 'application/json';
44
- const response = await fetchImpl(url, { method: request.method ?? 'GET', headers, body: request.body, signal: request.signal });
45
- if (!response.ok) {
46
- await discardBody(response);
47
- throw new GitHubApiError(response.status);
146
+ const deadline = callDeadline(request.signal, request.timeoutMs);
147
+ let response;
148
+ try {
149
+ response = await fetchImpl(url, { method: request.method ?? 'GET', headers, body: request.body, signal: deadline.signal });
150
+ }
151
+ catch {
152
+ // The fetch's own error would name the address or its cause; the reason is all a caller acts on.
153
+ throw deadline.noAnswer();
48
154
  }
49
- return response;
155
+ if (!response.ok)
156
+ throw await refusalOf(response);
157
+ return {
158
+ response,
159
+ async bytes(maxBytes) {
160
+ try {
161
+ return await readCapped(response, maxBytes);
162
+ }
163
+ catch (error) {
164
+ if (error instanceof ReadCapExceeded)
165
+ throw error;
166
+ throw deadline.noAnswer();
167
+ }
168
+ },
169
+ };
50
170
  }
51
- /** An answer's JSON, or `undefined` when it has no body (a 202 or a 204). */
52
- async function answerOf(response) {
53
- const text = await response.text();
54
- if (text === '')
171
+ /** An answer's JSON, read under {@link GITHUB_MAX_ANSWER_BYTES}, or `undefined` when it has no body (a 202 or a 204). */
172
+ async function answerOf(answer) {
173
+ let bytes;
174
+ try {
175
+ bytes = await answer.bytes(GITHUB_MAX_ANSWER_BYTES);
176
+ }
177
+ catch (error) {
178
+ if (error instanceof ReadCapExceeded)
179
+ throw new Error('GitHub answered a body past the size cap');
180
+ throw error;
181
+ }
182
+ if (bytes.byteLength === 0)
55
183
  return undefined;
56
184
  try {
57
- return JSON.parse(text);
185
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
58
186
  }
59
187
  catch {
60
188
  // The parser's message would quote the body.
@@ -64,7 +192,7 @@ async function answerOf(response) {
64
192
  /**
65
193
  * One call to `url` (an address under the API's base), answering its JSON, or `undefined` for an answer without a
66
194
  * body. Sends `Authorization: Bearer <token>`, `Accept: application/vnd.github+json` unless told otherwise,
67
- * `X-GitHub-Api-Version` and `User-Agent`.
195
+ * `X-GitHub-Api-Version` and `User-Agent`, and ends at its deadline.
68
196
  */
69
197
  export async function githubFetch(fetchImpl, url, request) {
70
198
  return answerOf(await githubSend(fetchImpl, url, request));
@@ -77,16 +205,16 @@ export function nextPage(link) {
77
205
  return match === null ? null : match[1];
78
206
  }
79
207
  /**
80
- * Each page of a listing as its JSON, following `Link: <...>; rel="next"`. A next page on another origin than the
81
- * first is refused, so the bearer never leaves the API it was sent to.
208
+ * Each page of a listing as its JSON, following `Link: <...>; rel="next"`, each page under a deadline of its own. A
209
+ * next page on another origin than the first is refused, so the bearer never leaves the API it was sent to.
82
210
  */
83
211
  export async function* githubPages(fetchImpl, url, request) {
84
212
  const origin = new URL(url).origin;
85
213
  let next = url;
86
214
  while (next !== null) {
87
- const response = await githubSend(fetchImpl, next, request);
88
- const link = response.headers.get('link');
89
- yield await answerOf(response);
215
+ const answer = await githubSend(fetchImpl, next, request);
216
+ const link = answer.response.headers.get('link');
217
+ yield await answerOf(answer);
90
218
  next = nextPage(link);
91
219
  if (next !== null && new URL(next).origin !== origin)
92
220
  throw new Error('GitHub named a next page on another origin');
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/app/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C,yBAAyB;AACzB,MAAM,CAAC,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAmBxD,yGAAyG;AACzG,MAAM,OAAO,cAAe,SAAQ,KAAK;IAElB;IADrB,qDAAqD;IACrD,YAAqB,MAAc;QACjC,KAAK,CAAC,mBAAmB,MAAM,EAAE,CAAC,CAAC;QADhB,WAAM,GAAN,MAAM,CAAQ;QAEjC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,2GAA2G;AAC3G,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,MAAM,GAAG,6BAA6B;IACjF,OAAO;QACL,aAAa,EAAE,UAAU,KAAK,EAAE;QAChC,MAAM;QACN,sBAAsB,EAAE,kBAAkB;QAC1C,YAAY,EAAE,oBAAoB;KACnC,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAkB;IAClD,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;IAC5E,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,SAAuB,EAAE,GAAW,EAAE,OAAsB;IAC3F,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAC7E,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAChI,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC5B,MAAM,IAAI,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,6EAA6E;AAC7E,KAAK,UAAU,QAAQ,CAAC,QAAkB;IACxC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAClC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;QAC7C,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAuB,EAAE,GAAW,EAAE,OAAsB;IAC5F,OAAO,QAAQ,CAAC,MAAM,UAAU,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,IAAmB;IAC1C,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,WAAW,CAAC,SAAuB,EAAE,GAAW,EAAE,OAAsB;IAC7F,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,IAAI,IAAI,GAAkB,GAAG,CAAC;IAC9B,OAAO,IAAI,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC1C,MAAM,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC/B,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACtH,CAAC;AACH,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,KAAK,CAAI,SAAuB,EAAE,GAAW,EAAE,OAAsB,EAAE,KAAsC;IACjI,MAAM,GAAG,GAAQ,EAAE,CAAC;IACpB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACxF,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAC,KAAc,EAAE,IAAY;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,wBAAwB,CAAC,CAAC;IAC1I,OAAO,KAAgC,CAAC;AAC1C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,OAAO,CAAC,KAAc,EAAE,IAAY;IAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,qBAAqB,CAAC,CAAC;IACzF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,QAAQ,CAAC,MAA+B,EAAE,GAAW,EAAE,IAAY;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAClG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,QAAQ,CAAC,MAA+B,EAAE,GAAW,EAAE,IAAY;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAClG,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/app/http.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview What a user token reaches (the person, the app's installations they can access, and the repositories
3
3
  * of an installation they can read) and the app's own installations, listed and removed with its JWT. Each function
4
- * takes `{ fetchImpl, apiBase }` last: `fetch` and GitHub's API when absent.
4
+ * takes `{ fetchImpl, apiBase, timeoutMs }` last: `fetch`, GitHub's API and a 30-second deadline a call when absent.
5
5
  * @module agentos-ext-github/app/installations
6
6
  */
7
7
  import type { GitHubAppCredential } from './credential.js';
@@ -27,22 +27,24 @@ export interface UserRepository {
27
27
  fullName: string;
28
28
  defaultBranch: string;
29
29
  }
30
+ /** An installation's account, as GitHub answers it and as a webhook event names it: a login, or an enterprise's slug, and an id. */
31
+ export declare function accountOf(value: unknown): GitHubAccount;
30
32
  /** The person a user token belongs to (`GET /user`): the GitHub id and login a link is made by. */
31
- export declare function userOfToken(token: string, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<GitHubUser>;
33
+ export declare function userOfToken(token: string, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<GitHubUser>;
32
34
  /** The app's installations a user token can access (`GET /user/installations`), across every page. */
33
- export declare function installationsOfUser(token: string, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
35
+ export declare function installationsOfUser(token: string, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
34
36
  /**
35
37
  * The repositories of an installation a user token can read (`GET /user/installations/{installation_id}/repositories`),
36
38
  * across every page: those the person owns, collaborates on or reaches through an organization.
37
39
  */
38
- export declare function repositoriesOfUser(token: string, installationId: number, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<UserRepository[]>;
40
+ export declare function repositoriesOfUser(token: string, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<UserRepository[]>;
39
41
  /** Every installation of the app (`GET /app/installations`, with its JWT), across every page. */
40
- export declare function listInstallations(credential: GitHubAppCredential, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
42
+ export declare function listInstallations(credential: GitHubAppCredential, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<GitHubInstallation[]>;
41
43
  /**
42
44
  * Uninstalls the app from an account (`DELETE /app/installations/{installation_id}`, with its JWT). Answers without an
43
45
  * error on 202 (GitHub has taken the removal and does it in the background at the pinned API version), on 204 (the
44
46
  * answer of earlier versions) and on 404 (the installation is already gone); the answer's body is never read. Any
45
47
  * other status throws {@link GitHubApiError}.
46
48
  */
47
- export declare function uninstallInstallation(credential: GitHubAppCredential, installationId: number, { fetchImpl, apiBase }?: GitHubApiOptions): Promise<void>;
49
+ export declare function uninstallInstallation(credential: GitHubAppCredential, installationId: number, { fetchImpl, apiBase, timeoutMs }?: GitHubApiOptions): Promise<void>;
48
50
  //# sourceMappingURL=installations.d.ts.map