@workerkit/core 0.3.7 → 0.3.9
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.
- package/CHANGELOG.md +29 -0
- package/README.md +10 -3
- package/SECURITY.md +34 -0
- package/dist/client.d.ts +5 -4
- package/dist/client.js +51 -18
- package/dist/client.js.map +1 -1
- package/dist/descriptors/decision-authoring.d.ts +0 -1
- package/dist/descriptors/decision-receipt.d.ts +0 -1
- package/dist/descriptors/directory.d.ts +0 -1
- package/dist/descriptors/directory.js +2 -2
- package/dist/descriptors/directory.js.map +1 -1
- package/dist/descriptors/index.d.ts +1 -2
- package/dist/descriptors/index.js +1 -1
- package/dist/descriptors/index.js.map +1 -1
- package/dist/descriptors/manage.d.ts +0 -1
- package/dist/descriptors/manage.js +60 -18
- package/dist/descriptors/manage.js.map +1 -1
- package/dist/descriptors/onboarding.d.ts +0 -1
- package/dist/descriptors/onboarding.js +1 -1
- package/dist/descriptors/onboarding.js.map +1 -1
- package/dist/descriptors/types.d.ts +2 -1
- package/dist/descriptors/types.js.map +1 -1
- package/dist/errors.d.ts +0 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +5 -1
- package/dist/execute.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/redact.d.ts +0 -1
- package/dist/zod.d.ts +0 -1
- package/docs/hybrid-workers.md +62 -0
- package/package.json +10 -5
- package/dist/client.d.ts.map +0 -1
- package/dist/descriptors/decision-authoring.d.ts.map +0 -1
- package/dist/descriptors/decision-receipt.d.ts.map +0 -1
- package/dist/descriptors/directory.d.ts.map +0 -1
- package/dist/descriptors/index.d.ts.map +0 -1
- package/dist/descriptors/manage.d.ts.map +0 -1
- package/dist/descriptors/onboarding.d.ts.map +0 -1
- package/dist/descriptors/types.d.ts.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/redact.d.ts.map +0 -1
- package/dist/zod.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,35 @@ and the package adheres to [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.9] - 2026-10-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `kit_install` accepts `githubSelection`: the GitHub accounts a new worker may use, each with all of its repositories or 1-500 selected repository IDs. Selections are validated before the request, and an empty account list is sent as-is to leave GitHub setup for later. `kit_install_preview` lists the connected accounts in `githubAccounts`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `onboarding_get` documents how the spend policy counts settled cost, BYOK fees and active reservations, when its daily and monthly windows reset, and how a refusal at the human-approved ceiling differs from the fleet budget.
|
|
18
|
+
- `app_connect` and `app_disconnect` list `nimble` among the credential providers.
|
|
19
|
+
|
|
20
|
+
## [0.3.8] - 2026-09-29
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
Adds worker_decision_set with strict classifier, answer and revision inputs. Hybrid instruction and receipt contracts preserve classification, agent evidence, separate usage and actual wallet charges. Explicit worker_run waits use a bounded per-request timeout; POST requests remain non-retryable.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Reserve authentication and computed headers regardless of casing; keep anonymous discovery free of bearer credentials.
|
|
29
|
+
- Exclude credentials and query strings from request logs; cancel active requests and retry waits promptly.
|
|
30
|
+
- Validate client resource limits and reject credential-bearing base URLs.
|
|
31
|
+
- Update Undici to the patched 7.29.1 release.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Build clean package contents and validate versions, release notes, installed packages and release tags before publication.
|
|
36
|
+
- Publish npm packages with provenance and attach the package to a matching GitHub Release.
|
|
37
|
+
|
|
9
38
|
## [0.3.7] - 2026-09-20
|
|
10
39
|
|
|
11
40
|
### Added
|
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
8
|
The shareable [WorkerKit](https://workerkit.ai) surface: an HTTP API client, the
|
|
9
|
-
declarative tool-descriptor registry (
|
|
9
|
+
declarative tool-descriptor registry (85 authenticated fleet-management tools +
|
|
10
10
|
9 anonymous kits-directory tools), and the single wire-execution path that turns
|
|
11
|
-
a descriptor plus parameters into
|
|
11
|
+
a descriptor plus parameters into one HTTP operation.
|
|
12
12
|
|
|
13
13
|
Deliberately MCP-free: descriptors carry MCP-shaped annotations and zod schemas,
|
|
14
14
|
but nothing here depends on an MCP SDK, so an MCP server, a CLI, or docs tooling
|
|
@@ -46,7 +46,7 @@ if (isSuccess(result)) {
|
|
|
46
46
|
|
|
47
47
|
// Authenticated: list your worker fleet with a manager key.
|
|
48
48
|
const list = byName("workers_list")!;
|
|
49
|
-
const fleet = await executeTool(client, list, {}, { token: process.env.
|
|
49
|
+
const fleet = await executeTool(client, list, {}, { token: process.env.WK_MANAGER_KEY });
|
|
50
50
|
|
|
51
51
|
await client.close();
|
|
52
52
|
```
|
|
@@ -61,6 +61,13 @@ whole-call deadline, bounded retries for idempotent methods only, a streamed
|
|
|
61
61
|
response-size cap, and quota/rate-limit header extraction. `scrubSecrets` helps
|
|
62
62
|
keep credentials out of logs.
|
|
63
63
|
|
|
64
|
+
## Classification followed by an agent task
|
|
65
|
+
|
|
66
|
+
A hybrid worker classifies a bounded source window and runs one language-model agent
|
|
67
|
+
on the selected batch. Core exposes the configuration and a single run receipt;
|
|
68
|
+
model execution, permissions and billing remain server responsibilities. See the
|
|
69
|
+
[hybrid worker guide](https://github.com/workerkit/core/blob/main/docs/hybrid-workers.md).
|
|
70
|
+
|
|
64
71
|
## Development
|
|
65
72
|
|
|
66
73
|
```bash
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Email **security@workerkit.ai**.
|
|
6
|
+
Please do not open public issues for security reports.
|
|
7
|
+
|
|
8
|
+
This document covers the `@workerkit/core` library specifically. For the platform, see the
|
|
9
|
+
[Security Overview](https://workerkit.ai/security), [Privacy Policy](https://workerkit.ai/privacy),
|
|
10
|
+
and [Terms of Service](https://workerkit.ai/terms).
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
`@workerkit/core` is a client library. It stores no credentials and keeps no state: bearer
|
|
15
|
+
tokens are supplied per call by the consumer and forwarded on the one request they authorize,
|
|
16
|
+
never logged or persisted. The `scrubSecrets` helper exists so consumers can redact key
|
|
17
|
+
material (`pe_mgr_` prefixes and bearer headers) from their own logs.
|
|
18
|
+
|
|
19
|
+
Properties relevant to a security review:
|
|
20
|
+
|
|
21
|
+
- Descriptors marked `anonymous` never forward an Authorization header, even when the caller
|
|
22
|
+
supplies a token.
|
|
23
|
+
- The HTTP client pins its origin at construction; descriptor paths cannot redirect a request
|
|
24
|
+
to another host.
|
|
25
|
+
- Responses are read through a hard size cap and per-attempt timeouts under a whole-call
|
|
26
|
+
deadline; retries apply to idempotent methods only and never to 429s.
|
|
27
|
+
- The package has two runtime dependencies (`undici`, `zod`), both exact-pinned.
|
|
28
|
+
|
|
29
|
+
## Supported versions
|
|
30
|
+
|
|
31
|
+
The latest published version. Releases are tag-driven from
|
|
32
|
+
[github.com/workerkit/core](https://github.com/workerkit/core) via npm trusted publishing, and
|
|
33
|
+
every version carries a provenance attestation linking the tarball to the commit and workflow
|
|
34
|
+
that built it.
|
package/dist/client.d.ts
CHANGED
|
@@ -29,15 +29,16 @@ export interface ClientOptions {
|
|
|
29
29
|
requestIdProvider?: () => string | undefined;
|
|
30
30
|
}
|
|
31
31
|
export interface RequestOpts {
|
|
32
|
+
/** Override only this request, e.g. an explicit run wait. POST is still never retried. */
|
|
33
|
+
timeoutMs?: number;
|
|
32
34
|
/** Bearer forwarded upstream. Omit for anonymous endpoints. */
|
|
33
35
|
token?: string;
|
|
34
36
|
params?: Record<string, unknown>;
|
|
35
37
|
body?: unknown;
|
|
36
38
|
signal?: AbortSignal;
|
|
37
39
|
/**
|
|
38
|
-
* Extra request headers
|
|
39
|
-
*
|
|
40
|
-
* so these can never override one.
|
|
40
|
+
* Extra request headers, such as Idempotency-Key. Authentication, routing and
|
|
41
|
+
* computed headers are reserved, regardless of casing. Use token for authentication.
|
|
41
42
|
*/
|
|
42
43
|
headers?: Record<string, string>;
|
|
43
44
|
}
|
|
@@ -85,6 +86,7 @@ export declare class PortEdenClient {
|
|
|
85
86
|
patch<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>>;
|
|
86
87
|
put<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>>;
|
|
87
88
|
delete<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>>;
|
|
89
|
+
private requestTimeout;
|
|
88
90
|
private doRequest;
|
|
89
91
|
private singleAttempt;
|
|
90
92
|
private buildUrl;
|
|
@@ -94,4 +96,3 @@ export declare class PortEdenClient {
|
|
|
94
96
|
}
|
|
95
97
|
/** Brand-facing alias — the same client, published under the WorkerKit name. */
|
|
96
98
|
export { PortEdenClient as WorkerKitClient };
|
|
97
|
-
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Agent, request as undiciRequest } from "undici";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
4
|
+
import { scrubSecrets } from "./redact.js";
|
|
3
5
|
/**
|
|
4
6
|
* The upstream response was well-formed but larger than the configured read budget, so we
|
|
5
7
|
* abandoned it mid-stream. Typed because it must not be retried: the response is deterministic,
|
|
@@ -29,6 +31,20 @@ const BACKOFF_MS = [200, 500]; // between attempts 1→2 and 2→3
|
|
|
29
31
|
// Ceiling on any Retry-After honoured below. Dormant today: no RETRYABLE_STATUS carries
|
|
30
32
|
// one, so quota.retryAfter is always null there. Read the 429 note above before changing.
|
|
31
33
|
const MAX_BACKOFF_MS = 2_000;
|
|
34
|
+
const RESERVED_HEADERS = new Set([
|
|
35
|
+
"authorization", "proxy-authorization", "host", "x-request-id",
|
|
36
|
+
"user-agent", "accept", "content-type", "content-length", "transfer-encoding",
|
|
37
|
+
]);
|
|
38
|
+
function positiveInteger(value, name) {
|
|
39
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > 2_147_483_647) {
|
|
40
|
+
throw new RangeError(`${name} must be a positive integer no greater than 2147483647`);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
function logPath(path, token) {
|
|
45
|
+
const pathname = path.split(/[?#]/, 1)[0] ?? "/";
|
|
46
|
+
return scrubSecrets(token ? pathname.split(token).join("[REDACTED]") : pathname);
|
|
47
|
+
}
|
|
32
48
|
const NOOP_LOGGER = {
|
|
33
49
|
info() {
|
|
34
50
|
/* no-op */
|
|
@@ -50,10 +66,14 @@ export class PortEdenClient {
|
|
|
50
66
|
logger;
|
|
51
67
|
requestIdProvider;
|
|
52
68
|
constructor(options) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
const base = new URL(options.baseUrl);
|
|
70
|
+
if (!["http:", "https:"].includes(base.protocol) || base.username || base.password) {
|
|
71
|
+
throw new Error("baseUrl must be an HTTP(S) URL without embedded credentials");
|
|
72
|
+
}
|
|
73
|
+
this.baseUrl = base.toString();
|
|
74
|
+
this.baseOrigin = base.origin;
|
|
75
|
+
this.timeoutMs = positiveInteger(options.timeoutMs ?? 30_000, "timeoutMs");
|
|
76
|
+
this.maxResponseBodyBytes = positiveInteger(options.maxResponseBodyBytes ?? 10 * 1024 * 1024, "maxResponseBodyBytes");
|
|
57
77
|
this.userAgent = options.userAgent;
|
|
58
78
|
this.logger = options.logger ?? NOOP_LOGGER;
|
|
59
79
|
this.requestIdProvider = options.requestIdProvider;
|
|
@@ -61,7 +81,7 @@ export class PortEdenClient {
|
|
|
61
81
|
keepAliveTimeout: 30_000,
|
|
62
82
|
keepAliveMaxTimeout: 60_000,
|
|
63
83
|
pipelining: 1,
|
|
64
|
-
connections: options.maxConnections ?? 16,
|
|
84
|
+
connections: positiveInteger(options.maxConnections ?? 16, "maxConnections"),
|
|
65
85
|
connect: { rejectUnauthorized: true },
|
|
66
86
|
// Defence in depth: the per-attempt AbortSignal below already bounds each
|
|
67
87
|
// call, but undici's own limits default to ~300s — pin them to the same
|
|
@@ -85,6 +105,10 @@ export class PortEdenClient {
|
|
|
85
105
|
async delete(path, opts) {
|
|
86
106
|
return this.doRequest("DELETE", path, opts);
|
|
87
107
|
}
|
|
108
|
+
requestTimeout(opts) {
|
|
109
|
+
return opts.timeoutMs !== undefined && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0
|
|
110
|
+
? Math.max(1, Math.floor(Math.min(300_000, opts.timeoutMs))) : this.timeoutMs;
|
|
111
|
+
}
|
|
88
112
|
async doRequest(method, path, opts) {
|
|
89
113
|
const isIdempotent = IDEMPOTENT_METHODS.has(method);
|
|
90
114
|
const maxAttempts = isIdempotent ? MAX_ATTEMPTS : 1;
|
|
@@ -92,7 +116,7 @@ export class PortEdenClient {
|
|
|
92
116
|
// worst case against a slow-then-failing upstream is ~3 x timeoutMs plus
|
|
93
117
|
// backoff, holding a concurrency slot for ~90s. Two timeout budgets is
|
|
94
118
|
// enough for one slow attempt plus a meaningful retry.
|
|
95
|
-
const totalDeadlineAt = performance.now() + this.
|
|
119
|
+
const totalDeadlineAt = performance.now() + this.requestTimeout(opts) * 2;
|
|
96
120
|
let result;
|
|
97
121
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
98
122
|
if (opts.signal?.aborted) {
|
|
@@ -101,6 +125,7 @@ export class PortEdenClient {
|
|
|
101
125
|
data: null,
|
|
102
126
|
quota: { limit: null, used: null, remaining: null, retryAfter: null },
|
|
103
127
|
requestId: randomUUID(),
|
|
128
|
+
retryable: false,
|
|
104
129
|
};
|
|
105
130
|
}
|
|
106
131
|
result = await this.singleAttempt(method, path, opts, attempt, totalDeadlineAt);
|
|
@@ -119,7 +144,7 @@ export class PortEdenClient {
|
|
|
119
144
|
this.logger.info({
|
|
120
145
|
msg: "api_retry_budget_exhausted",
|
|
121
146
|
method,
|
|
122
|
-
path,
|
|
147
|
+
path: logPath(path, opts.token),
|
|
123
148
|
status: result.status,
|
|
124
149
|
attempt,
|
|
125
150
|
requestId: result.requestId,
|
|
@@ -129,13 +154,18 @@ export class PortEdenClient {
|
|
|
129
154
|
this.logger.info({
|
|
130
155
|
msg: "api_retry",
|
|
131
156
|
method,
|
|
132
|
-
path,
|
|
157
|
+
path: logPath(path, opts.token),
|
|
133
158
|
status: result.status,
|
|
134
159
|
attempt,
|
|
135
160
|
nextDelayMs: delayMs,
|
|
136
161
|
requestId: result.requestId,
|
|
137
162
|
});
|
|
138
|
-
|
|
163
|
+
try {
|
|
164
|
+
await delay(delayMs, undefined, { signal: opts.signal });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// The next iteration returns the caller-cancel result without another attempt.
|
|
168
|
+
}
|
|
139
169
|
}
|
|
140
170
|
return result;
|
|
141
171
|
}
|
|
@@ -149,7 +179,7 @@ export class PortEdenClient {
|
|
|
149
179
|
const url = this.buildUrl(path, opts.params);
|
|
150
180
|
const start = performance.now();
|
|
151
181
|
const headers = {
|
|
152
|
-
...opts.headers,
|
|
182
|
+
...Object.fromEntries(Object.entries(opts.headers ?? {}).filter(([name]) => !RESERVED_HEADERS.has(name.toLowerCase()))),
|
|
153
183
|
"X-Request-Id": requestId,
|
|
154
184
|
Accept: "application/json",
|
|
155
185
|
};
|
|
@@ -179,7 +209,7 @@ export class PortEdenClient {
|
|
|
179
209
|
// THEN cap at timeoutMs. Flooring last would hand a 1s budget to a
|
|
180
210
|
// deployment configured with a shorter timeout.
|
|
181
211
|
const remainingMs = totalDeadlineAt - performance.now();
|
|
182
|
-
const timeoutSignal = AbortSignal.timeout(Math.min(this.
|
|
212
|
+
const timeoutSignal = AbortSignal.timeout(Math.floor(Math.min(this.requestTimeout(opts), Math.max(1_000, remainingMs))));
|
|
183
213
|
const signal = opts.signal
|
|
184
214
|
? AbortSignal.any([opts.signal, timeoutSignal])
|
|
185
215
|
: timeoutSignal;
|
|
@@ -190,6 +220,8 @@ export class PortEdenClient {
|
|
|
190
220
|
body: hasBody ? JSON.stringify(opts.body) : undefined,
|
|
191
221
|
signal,
|
|
192
222
|
dispatcher: this.agent,
|
|
223
|
+
headersTimeout: this.requestTimeout(opts),
|
|
224
|
+
bodyTimeout: this.requestTimeout(opts),
|
|
193
225
|
});
|
|
194
226
|
const status = response.statusCode;
|
|
195
227
|
const responseBody = await this.readBodyWithLimit(response.body);
|
|
@@ -213,7 +245,7 @@ export class PortEdenClient {
|
|
|
213
245
|
this.logger[logAtWarn ? "warn" : "info"]({
|
|
214
246
|
msg: "api_call",
|
|
215
247
|
method,
|
|
216
|
-
path,
|
|
248
|
+
path: logPath(path, opts.token),
|
|
217
249
|
status,
|
|
218
250
|
elapsed,
|
|
219
251
|
requestId,
|
|
@@ -236,11 +268,12 @@ export class PortEdenClient {
|
|
|
236
268
|
this.logger.error({
|
|
237
269
|
msg: "api_error",
|
|
238
270
|
method,
|
|
239
|
-
path,
|
|
271
|
+
path: logPath(path, opts.token),
|
|
240
272
|
elapsed,
|
|
241
273
|
requestId,
|
|
242
274
|
attempt,
|
|
243
|
-
|
|
275
|
+
// Validation errors can contain header values. Never log exception messages.
|
|
276
|
+
error: tooLarge ? "response_too_large" : callerAborted ? "canceled" : timedOut ? "timeout" : "request_failed",
|
|
244
277
|
callerAborted,
|
|
245
278
|
timedOut,
|
|
246
279
|
isTransport,
|
|
@@ -254,7 +287,7 @@ export class PortEdenClient {
|
|
|
254
287
|
// An oversized response reports status 0 like a transport error, but re-requesting it is
|
|
255
288
|
// guaranteed to fail the same way — mark it so the retry loop doesn't spend two more
|
|
256
289
|
// attempts (and both backoffs) proving that.
|
|
257
|
-
...(tooLarge ? { retryable: false } : {}),
|
|
290
|
+
...(tooLarge || callerAborted ? { retryable: false } : {}),
|
|
258
291
|
};
|
|
259
292
|
}
|
|
260
293
|
}
|
|
@@ -264,11 +297,11 @@ export class PortEdenClient {
|
|
|
264
297
|
// against future bugs (typo, unencoded user input flowing into path) that could
|
|
265
298
|
// pivot the proxy into making requests to an attacker-controlled host.
|
|
266
299
|
if (!path.startsWith("/")) {
|
|
267
|
-
throw new Error(
|
|
300
|
+
throw new Error("Invalid upstream path: must start with '/'");
|
|
268
301
|
}
|
|
269
302
|
const url = new URL(path, this.baseUrl);
|
|
270
|
-
if (url.origin !== this.baseOrigin) {
|
|
271
|
-
throw new Error(
|
|
303
|
+
if (url.origin !== this.baseOrigin || url.username || url.password) {
|
|
304
|
+
throw new Error("Upstream path must stay on the configured origin without embedded credentials");
|
|
272
305
|
}
|
|
273
306
|
if (params) {
|
|
274
307
|
for (const [key, value] of Object.entries(params)) {
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent, request as undiciRequest } from \"undici\";\nimport { randomUUID } from \"node:crypto\";\n\n/**\n * Minimal structured-logging surface the client emits into. Matches pino's\n * object-first calling convention (`logger.info({ msg, ... })`) so a pino\n * instance satisfies it directly; defaults to a no-op when omitted.\n */\nexport interface ClientLogger {\n info(o: object, msg?: string): void;\n warn(o: object, msg?: string): void;\n error(o: object, msg?: string): void;\n}\n\nexport interface ClientOptions {\n /** Upstream API base URL, e.g. \"https://api.workerkit.ai\". */\n baseUrl: string;\n /** Per-attempt request timeout in milliseconds. Default 30000. */\n timeoutMs?: number;\n /** Streamed response body cap in bytes. Default 10 MiB. */\n maxResponseBodyBytes?: number;\n /** Connection-pool size. Default 16. */\n maxConnections?: number;\n /** Emitted as the User-Agent header on every request when set. */\n userAgent?: string;\n /** Structured logger; defaults to a no-op. */\n logger?: ClientLogger;\n /**\n * Supplies the current correlation id (e.g. from AsyncLocalStorage in a\n * server). When it returns a value, each attempt's X-Request-Id is derived\n * from it (`<id>-aN`); otherwise a fresh UUID is minted per attempt.\n */\n requestIdProvider?: () => string | undefined;\n}\n\nexport interface RequestOpts {\n /** Bearer forwarded upstream. Omit for anonymous endpoints. */\n token?: string;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n /**\n * Extra request headers. Every computed header (Authorization, X-Request-Id,\n * User-Agent, Accept, Content-Type) is assigned AFTER this spread and wins,\n * so these can never override one.\n */\n headers?: Record<string, string>;\n}\n\nexport interface QuotaInfo {\n limit: number | null;\n used: number | null;\n remaining: number | null;\n retryAfter: number | null;\n}\n\nexport interface ApiResult<T = unknown> {\n status: number;\n data: T;\n quota: QuotaInfo;\n requestId: string;\n /**\n * Set to false when the failure is deterministic and re-issuing the identical request cannot\n * change the outcome. Overrides RETRYABLE_STATUSES, which classifies by status alone and so\n * can't tell a transport blip (status 0, worth retrying) from a local failure that happens to\n * report the same status. Absent means \"decide by status\", the normal path.\n */\n retryable?: boolean;\n}\n\n/**\n * The upstream response was well-formed but larger than the configured read budget, so we\n * abandoned it mid-stream. Typed because it must not be retried: the response is deterministic,\n * so the next two attempts re-download the same oversized body, burn the same seconds, and fail\n * identically.\n */\nexport class ResponseTooLargeError extends Error {\n constructor(public readonly limitBytes: number) {\n super(`Response body exceeded ${limitBytes} bytes`);\n this.name = \"ResponseTooLargeError\";\n }\n}\n\n// Retry policy — only applied to idempotent HTTP methods on transient upstream failures.\n// POST/PATCH are excluded because retrying a non-idempotent write could duplicate side\n// effects. Status 0 = transport error, 504 = local timeout (see doRequest).\n//\n// 429 must NEVER join RETRYABLE_STATUSES: upstream's Retry-After on a QUOTA_EXCEEDED 429\n// is secondsUntilReset (weeks, not seconds), MAX_BACKOFF_MS would clamp it to 2s, and a\n// billing-period quota wall would become a tight retry loop. It is surfaced to the\n// caller, which branches on errorCode, and never retried here.\nconst IDEMPOTENT_METHODS = new Set([\"GET\", \"PUT\", \"DELETE\"]);\nconst RETRYABLE_STATUSES = new Set([0, 502, 503, 504]);\nconst MAX_ATTEMPTS = 3;\nconst BACKOFF_MS = [200, 500]; // between attempts 1→2 and 2→3\n// Ceiling on any Retry-After honoured below. Dormant today: no RETRYABLE_STATUS carries\n// one, so quota.retryAfter is always null there. Read the 429 note above before changing.\nconst MAX_BACKOFF_MS = 2_000;\n\nconst NOOP_LOGGER: ClientLogger = {\n info() {\n /* no-op */\n },\n warn() {\n /* no-op */\n },\n error() {\n /* no-op */\n },\n};\n\nexport class PortEdenClient {\n private agent: Agent;\n private baseUrl: string;\n private baseOrigin: string;\n private timeoutMs: number;\n private maxResponseBodyBytes: number;\n private userAgent: string | undefined;\n private logger: ClientLogger;\n private requestIdProvider: (() => string | undefined) | undefined;\n\n constructor(options: ClientOptions) {\n this.baseUrl = options.baseUrl;\n this.baseOrigin = new URL(options.baseUrl).origin;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n this.maxResponseBodyBytes = options.maxResponseBodyBytes ?? 10 * 1024 * 1024;\n this.userAgent = options.userAgent;\n this.logger = options.logger ?? NOOP_LOGGER;\n this.requestIdProvider = options.requestIdProvider;\n this.agent = new Agent({\n keepAliveTimeout: 30_000,\n keepAliveMaxTimeout: 60_000,\n pipelining: 1,\n connections: options.maxConnections ?? 16,\n connect: { rejectUnauthorized: true },\n // Defence in depth: the per-attempt AbortSignal below already bounds each\n // call, but undici's own limits default to ~300s — pin them to the same\n // budget so a hung upstream can never outlive the request either way.\n headersTimeout: this.timeoutMs,\n bodyTimeout: this.timeoutMs,\n });\n }\n\n async get<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"GET\", path, opts);\n }\n\n async post<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"POST\", path, opts);\n }\n\n async patch<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"PATCH\", path, opts);\n }\n\n async put<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"PUT\", path, opts);\n }\n\n async delete<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"DELETE\", path, opts);\n }\n\n private async doRequest<T>(\n method: string,\n path: string,\n opts: RequestOpts\n ): Promise<ApiResult<T>> {\n const isIdempotent = IDEMPOTENT_METHODS.has(method);\n const maxAttempts = isIdempotent ? MAX_ATTEMPTS : 1;\n\n // Total deadline across ALL attempts and backoff delays. Without it, the\n // worst case against a slow-then-failing upstream is ~3 x timeoutMs plus\n // backoff, holding a concurrency slot for ~90s. Two timeout budgets is\n // enough for one slow attempt plus a meaningful retry.\n const totalDeadlineAt = performance.now() + this.timeoutMs * 2;\n\n let result: ApiResult<T> | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n if (opts.signal?.aborted) {\n return {\n status: 0,\n data: null as T,\n quota: { limit: null, used: null, remaining: null, retryAfter: null },\n requestId: randomUUID(),\n };\n }\n\n result = await this.singleAttempt<T>(method, path, opts, attempt, totalDeadlineAt);\n\n if (\n result.retryable === false ||\n !RETRYABLE_STATUSES.has(result.status) ||\n attempt === maxAttempts\n ) {\n return result;\n }\n\n const baseDelay = BACKOFF_MS[attempt - 1] ?? BACKOFF_MS[BACKOFF_MS.length - 1];\n const retryAfterMs = (result.quota.retryAfter ?? 0) * 1000;\n const delayMs = Math.min(Math.max(baseDelay, retryAfterMs), MAX_BACKOFF_MS);\n\n // Skip the retry when the remaining budget can't fit the backoff plus a\n // useful slice of the next attempt — return the transient failure instead\n // of holding the slot past the deadline.\n if (performance.now() + delayMs + 1_000 > totalDeadlineAt) {\n this.logger.info({\n msg: \"api_retry_budget_exhausted\",\n method,\n path,\n status: result.status,\n attempt,\n requestId: result.requestId,\n });\n return result;\n }\n\n this.logger.info({\n msg: \"api_retry\",\n method,\n path,\n status: result.status,\n attempt,\n nextDelayMs: delayMs,\n requestId: result.requestId,\n });\n\n await new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n }\n return result!;\n }\n\n private async singleAttempt<T>(\n method: string,\n path: string,\n opts: RequestOpts,\n attempt: number,\n totalDeadlineAt: number\n ): Promise<ApiResult<T>> {\n // Derive from the inbound correlation id so the caller's request log, the\n // api_call lines and the upstream API's own logs all join on one key; the\n // -aN suffix keeps each retry attempt distinguishable while staying unique\n // per upstream request.\n const correlationId = this.requestIdProvider?.();\n const requestId = correlationId ? `${correlationId}-a${attempt}` : randomUUID();\n const url = this.buildUrl(path, opts.params);\n const start = performance.now();\n\n const headers: Record<string, string> = {\n ...opts.headers,\n \"X-Request-Id\": requestId,\n Accept: \"application/json\",\n };\n if (this.userAgent) {\n headers[\"User-Agent\"] = this.userAgent;\n }\n if (opts.token) {\n headers.Authorization = `Bearer ${opts.token}`;\n }\n\n const hasBody = opts.body !== undefined && method !== \"GET\";\n if (hasBody) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n // Compose the caller's cancellation signal with a per-attempt timeout.\n // Using only opts.signal would let a long upstream call hang indefinitely;\n // using only the timeout would ignore client cancellation.\n //\n // The per-attempt budget is CLAMPED to what is left of the whole-call\n // deadline. Without the clamp, totalDeadlineAt gated only the DECISION to\n // start another attempt: the final attempt then got a fresh full timeoutMs\n // and the call could run ~3 x timeoutMs (~90s on defaults), holding a\n // concurrency slot and an upstream connection long after the caller had\n // given up.\n //\n // Order matters: floor at 1s (doRequest's gate already guarantees roughly\n // that much budget at attempt start, so this only guards arithmetic edges),\n // THEN cap at timeoutMs. Flooring last would hand a 1s budget to a\n // deployment configured with a shorter timeout.\n const remainingMs = totalDeadlineAt - performance.now();\n const timeoutSignal = AbortSignal.timeout(\n Math.min(this.timeoutMs, Math.max(1_000, remainingMs))\n );\n const signal = opts.signal\n ? AbortSignal.any([opts.signal, timeoutSignal])\n : timeoutSignal;\n\n try {\n const response = await undiciRequest(url, {\n method: method as \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\",\n headers,\n body: hasBody ? JSON.stringify(opts.body) : undefined,\n signal,\n dispatcher: this.agent,\n });\n\n const status = response.statusCode;\n const responseBody = await this.readBodyWithLimit(response.body);\n const elapsed = Math.round(performance.now() - start);\n\n let data: T;\n try {\n data = JSON.parse(responseBody) as T;\n } catch {\n data = (responseBody || null) as T;\n }\n\n const quota = this.extractQuota(response.headers as Record<string, string>);\n\n // An upstream 5xx is a service-level error: the request reached the API and\n // it failed to serve it. It goes through this success branch (undici resolved),\n // so without bumping the level here it would be logged at INFO — buried among\n // healthy 200s and invisible to log-based WARNING/ERROR views and alerts.\n // WARN (not ERROR): \"something is wrong with the service\", but often transient\n // and retried. 4xx stays at INFO — those are client/auth/quota/validation\n // outcomes, not outages.\n const logAtWarn = status >= 500;\n this.logger[logAtWarn ? \"warn\" : \"info\"]({\n msg: \"api_call\",\n method,\n path,\n status,\n elapsed,\n requestId,\n attempt,\n quotaRemaining: quota.remaining,\n });\n\n return { status, data, quota, requestId };\n } catch (err) {\n const elapsed = Math.round(performance.now() - start);\n // Distinguish caller-cancel from local timeout: timeoutSignal aborts when the\n // per-attempt budget is exhausted; opts.signal aborts when the caller\n // gave up. Treat caller-cancel as a non-retryable transport failure.\n const callerAborted = opts.signal?.aborted === true;\n const timedOut = !callerAborted && timeoutSignal.aborted;\n const isTransport =\n err instanceof Error &&\n \"code\" in err &&\n (err.code === \"ECONNRESET\" || err.code === \"ECONNREFUSED\" || err.code === \"ETIMEDOUT\");\n const tooLarge = err instanceof ResponseTooLargeError;\n\n this.logger.error({\n msg: \"api_error\",\n method,\n path,\n elapsed,\n requestId,\n attempt,\n error: err instanceof Error ? err.message : String(err),\n callerAborted,\n timedOut,\n isTransport,\n tooLarge,\n });\n\n return {\n status: timedOut ? 504 : 0,\n data: null as T,\n quota: { limit: null, used: null, remaining: null, retryAfter: null },\n requestId,\n // An oversized response reports status 0 like a transport error, but re-requesting it is\n // guaranteed to fail the same way — mark it so the retry loop doesn't spend two more\n // attempts (and both backoffs) proving that.\n ...(tooLarge ? { retryable: false } : {}),\n };\n }\n }\n\n private buildUrl(path: string, params?: Record<string, unknown>): string {\n // SSRF defence: reject absolute URLs and ensure the resolved origin matches the\n // configured upstream. Tool paths are hard-coded today, but this is a cheap guard\n // against future bugs (typo, unencoded user input flowing into path) that could\n // pivot the proxy into making requests to an attacker-controlled host.\n if (!path.startsWith(\"/\")) {\n throw new Error(`Invalid upstream path: must start with '/' (got ${path.slice(0, 64)})`);\n }\n const url = new URL(path, this.baseUrl);\n if (url.origin !== this.baseOrigin) {\n throw new Error(`Upstream origin mismatch: ${url.origin} != ${this.baseOrigin}`);\n }\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n // Array params repeat the key (?userIds=a&userIds=b) — that is what the\n // backend's [FromQuery] List<T> binding expects. The scalar String(value)\n // path below would comma-join an array into ONE value (\"a,b\"), which binds\n // as a single-element list and silently matches nothing upstream. Params\n // the backend takes as a comma-separated string instead are typed\n // z.string() by their tools and never reach this branch.\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== undefined && item !== null) url.searchParams.append(key, String(item));\n }\n continue;\n }\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n // Anchored rather than a bare parseInt, which is lenient: it takes the leading digit\n // run and ignores the rest, so an ISO-8601 timestamp (the shape of the resetsAt that\n // rides the QUOTA_EXCEEDED body) would parse to 2026 and read as a plausible 33-minute\n // backoff, with no NaN to catch it. Anchoring fails closed on that and on the HTTP-date\n // form alike. `-?` keeps any negative X-Monthly-* sentinel; a negative Retry-After is\n // rejected at the use site.\n private extractQuota(headers: Record<string, string | string[] | undefined>): QuotaInfo {\n const toNum = (key: string): number | null => {\n const val = headers[key];\n const str = (Array.isArray(val) ? val[0] : val)?.trim();\n if (!str || !/^-?\\d+$/.test(str)) return null;\n const n = Number(str);\n return Number.isSafeInteger(n) ? n : null;\n };\n\n return {\n limit: toNum(\"x-monthly-limit\"),\n used: toNum(\"x-monthly-used\"),\n remaining: toNum(\"x-monthly-remaining\"),\n retryAfter: toNum(\"retry-after\"),\n };\n }\n\n private async readBodyWithLimit(body: import(\"undici\").Dispatcher.ResponseData[\"body\"]): Promise<string> {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n for await (const chunk of body) {\n totalBytes += chunk.length;\n if (totalBytes > this.maxResponseBodyBytes) {\n // Consume remaining to avoid connection leaks, then throw\n body.destroy();\n throw new ResponseTooLargeError(this.maxResponseBodyBytes);\n }\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n async close(): Promise<void> {\n await this.agent.close();\n }\n}\n\n/** Brand-facing alias — the same client, published under the WorkerKit name. */\nexport { PortEdenClient as WorkerKitClient };\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent, request as undiciRequest } from \"undici\";\nimport { randomUUID } from \"node:crypto\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { scrubSecrets } from \"./redact.js\";\n\n/**\n * Minimal structured-logging surface the client emits into. Matches pino's\n * object-first calling convention (`logger.info({ msg, ... })`) so a pino\n * instance satisfies it directly; defaults to a no-op when omitted.\n */\nexport interface ClientLogger {\n info(o: object, msg?: string): void;\n warn(o: object, msg?: string): void;\n error(o: object, msg?: string): void;\n}\n\nexport interface ClientOptions {\n /** Upstream API base URL, e.g. \"https://api.workerkit.ai\". */\n baseUrl: string;\n /** Per-attempt request timeout in milliseconds. Default 30000. */\n timeoutMs?: number;\n /** Streamed response body cap in bytes. Default 10 MiB. */\n maxResponseBodyBytes?: number;\n /** Connection-pool size. Default 16. */\n maxConnections?: number;\n /** Emitted as the User-Agent header on every request when set. */\n userAgent?: string;\n /** Structured logger; defaults to a no-op. */\n logger?: ClientLogger;\n /**\n * Supplies the current correlation id (e.g. from AsyncLocalStorage in a\n * server). When it returns a value, each attempt's X-Request-Id is derived\n * from it (`<id>-aN`); otherwise a fresh UUID is minted per attempt.\n */\n requestIdProvider?: () => string | undefined;\n}\n\nexport interface RequestOpts {\n /** Override only this request, e.g. an explicit run wait. POST is still never retried. */\n timeoutMs?: number;\n /** Bearer forwarded upstream. Omit for anonymous endpoints. */\n token?: string;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n /**\n * Extra request headers, such as Idempotency-Key. Authentication, routing and\n * computed headers are reserved, regardless of casing. Use token for authentication.\n */\n headers?: Record<string, string>;\n}\n\nexport interface QuotaInfo {\n limit: number | null;\n used: number | null;\n remaining: number | null;\n retryAfter: number | null;\n}\n\nexport interface ApiResult<T = unknown> {\n status: number;\n data: T;\n quota: QuotaInfo;\n requestId: string;\n /**\n * Set to false when the failure is deterministic and re-issuing the identical request cannot\n * change the outcome. Overrides RETRYABLE_STATUSES, which classifies by status alone and so\n * can't tell a transport blip (status 0, worth retrying) from a local failure that happens to\n * report the same status. Absent means \"decide by status\", the normal path.\n */\n retryable?: boolean;\n}\n\n/**\n * The upstream response was well-formed but larger than the configured read budget, so we\n * abandoned it mid-stream. Typed because it must not be retried: the response is deterministic,\n * so the next two attempts re-download the same oversized body, burn the same seconds, and fail\n * identically.\n */\nexport class ResponseTooLargeError extends Error {\n constructor(public readonly limitBytes: number) {\n super(`Response body exceeded ${limitBytes} bytes`);\n this.name = \"ResponseTooLargeError\";\n }\n}\n\n// Retry policy — only applied to idempotent HTTP methods on transient upstream failures.\n// POST/PATCH are excluded because retrying a non-idempotent write could duplicate side\n// effects. Status 0 = transport error, 504 = local timeout (see doRequest).\n//\n// 429 must NEVER join RETRYABLE_STATUSES: upstream's Retry-After on a QUOTA_EXCEEDED 429\n// is secondsUntilReset (weeks, not seconds), MAX_BACKOFF_MS would clamp it to 2s, and a\n// billing-period quota wall would become a tight retry loop. It is surfaced to the\n// caller, which branches on errorCode, and never retried here.\nconst IDEMPOTENT_METHODS = new Set([\"GET\", \"PUT\", \"DELETE\"]);\nconst RETRYABLE_STATUSES = new Set([0, 502, 503, 504]);\nconst MAX_ATTEMPTS = 3;\nconst BACKOFF_MS = [200, 500]; // between attempts 1→2 and 2→3\n// Ceiling on any Retry-After honoured below. Dormant today: no RETRYABLE_STATUS carries\n// one, so quota.retryAfter is always null there. Read the 429 note above before changing.\nconst MAX_BACKOFF_MS = 2_000;\nconst RESERVED_HEADERS = new Set([\n \"authorization\", \"proxy-authorization\", \"host\", \"x-request-id\",\n \"user-agent\", \"accept\", \"content-type\", \"content-length\", \"transfer-encoding\",\n]);\n\nfunction positiveInteger(value: number, name: string): number {\n if (!Number.isSafeInteger(value) || value <= 0 || value > 2_147_483_647) {\n throw new RangeError(`${name} must be a positive integer no greater than 2147483647`);\n }\n return value;\n}\n\nfunction logPath(path: string, token?: string): string {\n const pathname = path.split(/[?#]/, 1)[0] ?? \"/\";\n return scrubSecrets(token ? pathname.split(token).join(\"[REDACTED]\") : pathname);\n}\n\nconst NOOP_LOGGER: ClientLogger = {\n info() {\n /* no-op */\n },\n warn() {\n /* no-op */\n },\n error() {\n /* no-op */\n },\n};\n\nexport class PortEdenClient {\n private agent: Agent;\n private baseUrl: string;\n private baseOrigin: string;\n private timeoutMs: number;\n private maxResponseBodyBytes: number;\n private userAgent: string | undefined;\n private logger: ClientLogger;\n private requestIdProvider: (() => string | undefined) | undefined;\n\n constructor(options: ClientOptions) {\n const base = new URL(options.baseUrl);\n if (![\"http:\", \"https:\"].includes(base.protocol) || base.username || base.password) {\n throw new Error(\"baseUrl must be an HTTP(S) URL without embedded credentials\");\n }\n this.baseUrl = base.toString();\n this.baseOrigin = base.origin;\n this.timeoutMs = positiveInteger(options.timeoutMs ?? 30_000, \"timeoutMs\");\n this.maxResponseBodyBytes = positiveInteger(options.maxResponseBodyBytes ?? 10 * 1024 * 1024, \"maxResponseBodyBytes\");\n this.userAgent = options.userAgent;\n this.logger = options.logger ?? NOOP_LOGGER;\n this.requestIdProvider = options.requestIdProvider;\n this.agent = new Agent({\n keepAliveTimeout: 30_000,\n keepAliveMaxTimeout: 60_000,\n pipelining: 1,\n connections: positiveInteger(options.maxConnections ?? 16, \"maxConnections\"),\n connect: { rejectUnauthorized: true },\n // Defence in depth: the per-attempt AbortSignal below already bounds each\n // call, but undici's own limits default to ~300s — pin them to the same\n // budget so a hung upstream can never outlive the request either way.\n headersTimeout: this.timeoutMs,\n bodyTimeout: this.timeoutMs,\n });\n }\n\n async get<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"GET\", path, opts);\n }\n\n async post<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"POST\", path, opts);\n }\n\n async patch<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"PATCH\", path, opts);\n }\n\n async put<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"PUT\", path, opts);\n }\n\n async delete<T = unknown>(path: string, opts: RequestOpts): Promise<ApiResult<T>> {\n return this.doRequest<T>(\"DELETE\", path, opts);\n }\n\n private requestTimeout(opts: RequestOpts): number {\n return opts.timeoutMs !== undefined && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0\n ? Math.max(1, Math.floor(Math.min(300_000, opts.timeoutMs))) : this.timeoutMs;\n }\n\n private async doRequest<T>(\n method: string,\n path: string,\n opts: RequestOpts\n ): Promise<ApiResult<T>> {\n const isIdempotent = IDEMPOTENT_METHODS.has(method);\n const maxAttempts = isIdempotent ? MAX_ATTEMPTS : 1;\n\n // Total deadline across ALL attempts and backoff delays. Without it, the\n // worst case against a slow-then-failing upstream is ~3 x timeoutMs plus\n // backoff, holding a concurrency slot for ~90s. Two timeout budgets is\n // enough for one slow attempt plus a meaningful retry.\n const totalDeadlineAt = performance.now() + this.requestTimeout(opts) * 2;\n\n let result: ApiResult<T> | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n if (opts.signal?.aborted) {\n return {\n status: 0,\n data: null as T,\n quota: { limit: null, used: null, remaining: null, retryAfter: null },\n requestId: randomUUID(),\n retryable: false,\n };\n }\n\n result = await this.singleAttempt<T>(method, path, opts, attempt, totalDeadlineAt);\n\n if (\n result.retryable === false ||\n !RETRYABLE_STATUSES.has(result.status) ||\n attempt === maxAttempts\n ) {\n return result;\n }\n\n const baseDelay = BACKOFF_MS[attempt - 1] ?? BACKOFF_MS[BACKOFF_MS.length - 1];\n const retryAfterMs = (result.quota.retryAfter ?? 0) * 1000;\n const delayMs = Math.min(Math.max(baseDelay, retryAfterMs), MAX_BACKOFF_MS);\n\n // Skip the retry when the remaining budget can't fit the backoff plus a\n // useful slice of the next attempt — return the transient failure instead\n // of holding the slot past the deadline.\n if (performance.now() + delayMs + 1_000 > totalDeadlineAt) {\n this.logger.info({\n msg: \"api_retry_budget_exhausted\",\n method,\n path: logPath(path, opts.token),\n status: result.status,\n attempt,\n requestId: result.requestId,\n });\n return result;\n }\n\n this.logger.info({\n msg: \"api_retry\",\n method,\n path: logPath(path, opts.token),\n status: result.status,\n attempt,\n nextDelayMs: delayMs,\n requestId: result.requestId,\n });\n\n try {\n await delay(delayMs, undefined, { signal: opts.signal });\n } catch {\n // The next iteration returns the caller-cancel result without another attempt.\n }\n }\n return result!;\n }\n\n private async singleAttempt<T>(\n method: string,\n path: string,\n opts: RequestOpts,\n attempt: number,\n totalDeadlineAt: number\n ): Promise<ApiResult<T>> {\n // Derive from the inbound correlation id so the caller's request log, the\n // api_call lines and the upstream API's own logs all join on one key; the\n // -aN suffix keeps each retry attempt distinguishable while staying unique\n // per upstream request.\n const correlationId = this.requestIdProvider?.();\n const requestId = correlationId ? `${correlationId}-a${attempt}` : randomUUID();\n const url = this.buildUrl(path, opts.params);\n const start = performance.now();\n\n const headers: Record<string, string> = {\n ...Object.fromEntries(Object.entries(opts.headers ?? {}).filter(([name]) => !RESERVED_HEADERS.has(name.toLowerCase()))),\n \"X-Request-Id\": requestId,\n Accept: \"application/json\",\n };\n if (this.userAgent) {\n headers[\"User-Agent\"] = this.userAgent;\n }\n if (opts.token) {\n headers.Authorization = `Bearer ${opts.token}`;\n }\n\n const hasBody = opts.body !== undefined && method !== \"GET\";\n if (hasBody) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n // Compose the caller's cancellation signal with a per-attempt timeout.\n // Using only opts.signal would let a long upstream call hang indefinitely;\n // using only the timeout would ignore client cancellation.\n //\n // The per-attempt budget is CLAMPED to what is left of the whole-call\n // deadline. Without the clamp, totalDeadlineAt gated only the DECISION to\n // start another attempt: the final attempt then got a fresh full timeoutMs\n // and the call could run ~3 x timeoutMs (~90s on defaults), holding a\n // concurrency slot and an upstream connection long after the caller had\n // given up.\n //\n // Order matters: floor at 1s (doRequest's gate already guarantees roughly\n // that much budget at attempt start, so this only guards arithmetic edges),\n // THEN cap at timeoutMs. Flooring last would hand a 1s budget to a\n // deployment configured with a shorter timeout.\n const remainingMs = totalDeadlineAt - performance.now();\n const timeoutSignal = AbortSignal.timeout(\n Math.floor(Math.min(this.requestTimeout(opts), Math.max(1_000, remainingMs)))\n );\n const signal = opts.signal\n ? AbortSignal.any([opts.signal, timeoutSignal])\n : timeoutSignal;\n\n try {\n const response = await undiciRequest(url, {\n method: method as \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\",\n headers,\n body: hasBody ? JSON.stringify(opts.body) : undefined,\n signal,\n dispatcher: this.agent,\n headersTimeout: this.requestTimeout(opts),\n bodyTimeout: this.requestTimeout(opts),\n });\n\n const status = response.statusCode;\n const responseBody = await this.readBodyWithLimit(response.body);\n const elapsed = Math.round(performance.now() - start);\n\n let data: T;\n try {\n data = JSON.parse(responseBody) as T;\n } catch {\n data = (responseBody || null) as T;\n }\n\n const quota = this.extractQuota(response.headers as Record<string, string>);\n\n // An upstream 5xx is a service-level error: the request reached the API and\n // it failed to serve it. It goes through this success branch (undici resolved),\n // so without bumping the level here it would be logged at INFO — buried among\n // healthy 200s and invisible to log-based WARNING/ERROR views and alerts.\n // WARN (not ERROR): \"something is wrong with the service\", but often transient\n // and retried. 4xx stays at INFO — those are client/auth/quota/validation\n // outcomes, not outages.\n const logAtWarn = status >= 500;\n this.logger[logAtWarn ? \"warn\" : \"info\"]({\n msg: \"api_call\",\n method,\n path: logPath(path, opts.token),\n status,\n elapsed,\n requestId,\n attempt,\n quotaRemaining: quota.remaining,\n });\n\n return { status, data, quota, requestId };\n } catch (err) {\n const elapsed = Math.round(performance.now() - start);\n // Distinguish caller-cancel from local timeout: timeoutSignal aborts when the\n // per-attempt budget is exhausted; opts.signal aborts when the caller\n // gave up. Treat caller-cancel as a non-retryable transport failure.\n const callerAborted = opts.signal?.aborted === true;\n const timedOut = !callerAborted && timeoutSignal.aborted;\n const isTransport =\n err instanceof Error &&\n \"code\" in err &&\n (err.code === \"ECONNRESET\" || err.code === \"ECONNREFUSED\" || err.code === \"ETIMEDOUT\");\n const tooLarge = err instanceof ResponseTooLargeError;\n\n this.logger.error({\n msg: \"api_error\",\n method,\n path: logPath(path, opts.token),\n elapsed,\n requestId,\n attempt,\n // Validation errors can contain header values. Never log exception messages.\n error: tooLarge ? \"response_too_large\" : callerAborted ? \"canceled\" : timedOut ? \"timeout\" : \"request_failed\",\n callerAborted,\n timedOut,\n isTransport,\n tooLarge,\n });\n\n return {\n status: timedOut ? 504 : 0,\n data: null as T,\n quota: { limit: null, used: null, remaining: null, retryAfter: null },\n requestId,\n // An oversized response reports status 0 like a transport error, but re-requesting it is\n // guaranteed to fail the same way — mark it so the retry loop doesn't spend two more\n // attempts (and both backoffs) proving that.\n ...(tooLarge || callerAborted ? { retryable: false } : {}),\n };\n }\n }\n\n private buildUrl(path: string, params?: Record<string, unknown>): string {\n // SSRF defence: reject absolute URLs and ensure the resolved origin matches the\n // configured upstream. Tool paths are hard-coded today, but this is a cheap guard\n // against future bugs (typo, unencoded user input flowing into path) that could\n // pivot the proxy into making requests to an attacker-controlled host.\n if (!path.startsWith(\"/\")) {\n throw new Error(\"Invalid upstream path: must start with '/'\");\n }\n const url = new URL(path, this.baseUrl);\n if (url.origin !== this.baseOrigin || url.username || url.password) {\n throw new Error(\"Upstream path must stay on the configured origin without embedded credentials\");\n }\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value === undefined || value === null) continue;\n // Array params repeat the key (?userIds=a&userIds=b) — that is what the\n // backend's [FromQuery] List<T> binding expects. The scalar String(value)\n // path below would comma-join an array into ONE value (\"a,b\"), which binds\n // as a single-element list and silently matches nothing upstream. Params\n // the backend takes as a comma-separated string instead are typed\n // z.string() by their tools and never reach this branch.\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== undefined && item !== null) url.searchParams.append(key, String(item));\n }\n continue;\n }\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n // Anchored rather than a bare parseInt, which is lenient: it takes the leading digit\n // run and ignores the rest, so an ISO-8601 timestamp (the shape of the resetsAt that\n // rides the QUOTA_EXCEEDED body) would parse to 2026 and read as a plausible 33-minute\n // backoff, with no NaN to catch it. Anchoring fails closed on that and on the HTTP-date\n // form alike. `-?` keeps any negative X-Monthly-* sentinel; a negative Retry-After is\n // rejected at the use site.\n private extractQuota(headers: Record<string, string | string[] | undefined>): QuotaInfo {\n const toNum = (key: string): number | null => {\n const val = headers[key];\n const str = (Array.isArray(val) ? val[0] : val)?.trim();\n if (!str || !/^-?\\d+$/.test(str)) return null;\n const n = Number(str);\n return Number.isSafeInteger(n) ? n : null;\n };\n\n return {\n limit: toNum(\"x-monthly-limit\"),\n used: toNum(\"x-monthly-used\"),\n remaining: toNum(\"x-monthly-remaining\"),\n retryAfter: toNum(\"retry-after\"),\n };\n }\n\n private async readBodyWithLimit(body: import(\"undici\").Dispatcher.ResponseData[\"body\"]): Promise<string> {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n for await (const chunk of body) {\n totalBytes += chunk.length;\n if (totalBytes > this.maxResponseBodyBytes) {\n // Consume remaining to avoid connection leaks, then throw\n body.destroy();\n throw new ResponseTooLargeError(this.maxResponseBodyBytes);\n }\n chunks.push(Buffer.from(chunk));\n }\n return Buffer.concat(chunks).toString(\"utf-8\");\n }\n\n async close(): Promise<void> {\n await this.agent.close();\n }\n}\n\n/** Brand-facing alias — the same client, published under the WorkerKit name. */\nexport { PortEdenClient as WorkerKitClient };\n"]}
|
|
@@ -22,4 +22,3 @@ export declare function publisherPageData(data: unknown, params: Record<string,
|
|
|
22
22
|
export declare function publisherPageFooter(data: unknown): string | undefined;
|
|
23
23
|
export declare const directoryDescriptors: readonly ToolDescriptor[];
|
|
24
24
|
export {};
|
|
25
|
-
//# sourceMappingURL=directory.d.ts.map
|
|
@@ -137,7 +137,7 @@ const searchKits = {
|
|
|
137
137
|
appCategory: z.string().optional().describe("Capability-category code, e.g. 'team-collaboration': matches kits that define that install-time slot OR pin one of its member apps — broader than the app filter. Unknown codes error (the error names the fix). Valid codes: directory_overview → appCategories."),
|
|
138
138
|
model: z.string().max(80).optional().describe("Substring match on a scored model name, e.g. 'sonnet' or 'gpt'. Valid names: directory_overview → models."),
|
|
139
139
|
trigger: z.enum(["onDemand", "scheduled", "event"]).optional().describe("How the kit's worker runs: onDemand (a human or agent triggers it), scheduled (ships recurring schedules), event (webhook-triggered)."),
|
|
140
|
-
modelType: z.enum(["language", "decision"]).optional().describe("language = an instruction the installer runs on a model of their choice; decision = a routing table the decision model applies per item (triage, sorting, labelling, filtering — no instruction, no model to pick). Omit for both."),
|
|
140
|
+
modelType: z.enum(["language", "decision", "hybrid"]).optional().describe("hybrid = bounded classification followed by one language-model agent; language = an instruction the installer runs on a model of their choice; decision = a routing table the decision model applies per item (triage, sorting, labelling, filtering — no instruction, no model to pick). Omit for both."),
|
|
141
141
|
publisher: z.string().optional().describe("Publisher slug (publisherSlug on any card) — that publisher's kits only."),
|
|
142
142
|
sort: z.enum(["downloads", "new", "name", "stars"]).default("downloads").describe("downloads = most installed (the popularity signal), new = latest published, name = A-Z, stars = most starred."),
|
|
143
143
|
page: z.number().int().min(1).default(1).describe("1-based page number."),
|
|
@@ -156,7 +156,7 @@ const searchKits = {
|
|
|
156
156
|
const getKit = {
|
|
157
157
|
name: "kit_get",
|
|
158
158
|
title: "Get Kit",
|
|
159
|
-
description: "One kit's full public dossier — what a human, or an agent advising one, evaluates before installing. Fields: description + appDescriptions (the author's per-app / per-tool notes); modelType ('language', the default, or 'decision'); instructionContent — a language kit's job instruction VERBATIM, the core of what you are evaluating (empty when isProtected, with instructionLength still real; startCommand / endCommand / whenToUse are null on a protected kit, indistinguishable from 'none'); on a DECISION kit evaluate decisionNarration instead (what it reads, the questions it asks each item, the rules that route them, what happens below the confidence floor — served on protected kits too) and decisionSetup (the questions its installer answers); permissionsManifest — the EXACT data access an install grants (per-app operations / fields / scopes; the numeric value is authoritative, names are display) — summarize THIS when asked what the kit can touch; requiredInputs + memorySetup — the install form (fields the kit needs, questions whose answers become the worker's memory); memoryProfile ('contextual' = the worker sees its own recent runs) and selfFactsEnabled (it saves durable facts on its own); schedules / triggers / usageWindows; skillResources — reference docs the worker fetches on demand (key / kind / name always; description / contentLength unless protected; bodies via includeResourceBodies); hasExternalMcpShells — TRUE means installing creates an MCP gateway in the installer's account pointing at the publisher's own upstream URL (permissionsManifest.mcpServers.shells): surface it when recommending; scan — the security scan of the text being served: outcome 'cleared' or 'underReview' (both are live, installable listings — 'underReview' means a reviewer has not settled a raised check, never that the kit is unsafe), scannedAtUtc (null = published before the scanner existed, most of the catalog), checks (the families that ran on THIS kit — 'cleared' on ['static'] alone is weaker than on all three) and recommendation ('safe' | 'caution' | 'doNotInstall', the SCANNER's own advice, independent of outcome and possibly disagreeing with it: attribute it to SkillSpector, and read 'caution' as 'worth a look' — it is also emitted when the analysis was incomplete); scan is absent when there is nothing to report and never carries findings; modelScores (with notes), downloadCount / starCount, and pageUrl — the install page to hand to the human. 404 = no published kit with that slug." +
|
|
159
|
+
description: "One kit's full public dossier — what a human, or an agent advising one, evaluates before installing. Fields: description + appDescriptions (the author's per-app / per-tool notes); modelType ('hybrid' combines classification and an instruction; 'language', the default, or 'decision'); instructionContent — a language kit's job instruction VERBATIM, the core of what you are evaluating (empty when isProtected, with instructionLength still real; startCommand / endCommand / whenToUse are null on a protected kit, indistinguishable from 'none'); on a DECISION kit evaluate decisionNarration instead (what it reads, the questions it asks each item, the rules that route them, what happens below the confidence floor — served on protected kits too) and decisionSetup (the questions its installer answers); permissionsManifest — the EXACT data access an install grants (per-app operations / fields / scopes; the numeric value is authoritative, names are display) — summarize THIS when asked what the kit can touch; requiredInputs + memorySetup — the install form (fields the kit needs, questions whose answers become the worker's memory); memoryProfile ('contextual' = the worker sees its own recent runs) and selfFactsEnabled (it saves durable facts on its own); schedules / triggers / usageWindows; skillResources — reference docs the worker fetches on demand (key / kind / name always; description / contentLength unless protected; bodies via includeResourceBodies); hasExternalMcpShells — TRUE means installing creates an MCP gateway in the installer's account pointing at the publisher's own upstream URL (permissionsManifest.mcpServers.shells): surface it when recommending; scan — the security scan of the text being served: outcome 'cleared' or 'underReview' (both are live, installable listings — 'underReview' means a reviewer has not settled a raised check, never that the kit is unsafe), scannedAtUtc (null = published before the scanner existed, most of the catalog), checks (the families that ran on THIS kit — 'cleared' on ['static'] alone is weaker than on all three) and recommendation ('safe' | 'caution' | 'doNotInstall', the SCANNER's own advice, independent of outcome and possibly disagreeing with it: attribute it to SkillSpector, and read 'caution' as 'worth a look' — it is also emitted when the analysis was incomplete); scan is absent when there is nothing to report and never carries findings; modelScores (with notes), downloadCount / starCount, and pageUrl — the install page to hand to the human. 404 = no published kit with that slug." +
|
|
160
160
|
DIRECTORY_NOTE,
|
|
161
161
|
auth: "anonymous",
|
|
162
162
|
method: "get",
|