@agent-api/sdk 1.2.1 → 1.2.3
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 +13 -0
- package/README.md +3 -2
- package/dist/internal/http.js +47 -5
- package/dist/local/core.js +38 -7
- package/dist/types/common.d.ts +2 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist-cjs/internal/http.js +47 -5
- package/dist-cjs/local/core.js +37 -6
- package/dist-cjs/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog — @agent-api/sdk
|
|
2
2
|
|
|
3
|
+
## 1.2.3
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Hardened local workdir scans so broken symlinks and files that disappear during recursive list, grep, and summarize operations do not abort the scan.
|
|
8
|
+
|
|
9
|
+
## 1.2.2
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added `RequestOptions.signal` so CLI, TUI, and desktop integrations can abort in-flight HTTP requests and response streams before a response ID is available.
|
|
14
|
+
- Documented and tested response cancellation behavior alongside the existing `responses.cancel(responseID)` backend cancellation API.
|
|
15
|
+
|
|
3
16
|
## 1.2.1
|
|
4
17
|
|
|
5
18
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Production JavaScript/TypeScript SDK for the Managed Agent API.
|
|
4
4
|
|
|
5
|
-
**Published on npm:** [`@agent-api/sdk`](https://www.npmjs.com/package/@agent-api/sdk) (v1.2.
|
|
5
|
+
**Published on npm:** [`@agent-api/sdk`](https://www.npmjs.com/package/@agent-api/sdk) (v1.2.2)
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -257,6 +257,7 @@ const context = await createLocalContextPackage(project, {
|
|
|
257
257
|
|
|
258
258
|
- **Retries:** automatic exponential backoff for network failures, 429, and 5xx (default 2 retries).
|
|
259
259
|
- **Timeouts:** 10 minute default for requests; 1 hour for streaming agent runs (configurable via `timeout` / `streamTimeout`).
|
|
260
|
+
- **Cancellation:** pass `signal` in request options to abort local HTTP waiting, and call `client.responses.cancel(responseID)` for backend best-effort cancellation after a response ID exists.
|
|
260
261
|
- **Typed errors:** `AuthenticationError`, `RateLimitError`, `NotFoundError`, `BadRequestError`, etc.
|
|
261
262
|
- **Pagination:** `listPage` returns a cursor page; `listIterator` auto-fetches all pages.
|
|
262
263
|
|
|
@@ -273,7 +274,7 @@ const stream = await client.responses.create({
|
|
|
273
274
|
preset: "fast-search",
|
|
274
275
|
input: "Summarize today's AI news.",
|
|
275
276
|
stream: true,
|
|
276
|
-
});
|
|
277
|
+
}, { signal: abortController.signal });
|
|
277
278
|
|
|
278
279
|
for await (const event of stream) {
|
|
279
280
|
if (event.type === "response.output_text.delta") {
|
package/dist/internal/http.js
CHANGED
|
@@ -33,6 +33,9 @@ export class HTTPClient {
|
|
|
33
33
|
return await this.fetchOnce(method, path, body, options, stream, rawBody);
|
|
34
34
|
}
|
|
35
35
|
catch (error) {
|
|
36
|
+
if (options.signal?.aborted) {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
36
39
|
if (!(error instanceof APIError) || attempt >= maxRetries) {
|
|
37
40
|
throw error;
|
|
38
41
|
}
|
|
@@ -43,14 +46,29 @@ export class HTTPClient {
|
|
|
43
46
|
}
|
|
44
47
|
attempt += 1;
|
|
45
48
|
const delayMs = retryDelayMs(error, attempt);
|
|
46
|
-
await sleep(delayMs);
|
|
49
|
+
await sleep(delayMs, options.signal);
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
async fetchOnce(method, path, body, options, stream, rawBody) {
|
|
51
54
|
const controller = new AbortController();
|
|
52
55
|
const timeout = options.timeout ?? (stream ? this.options.streamTimeout : this.options.timeout);
|
|
53
|
-
|
|
56
|
+
let timedOut = false;
|
|
57
|
+
let callerAborted = options.signal?.aborted ?? false;
|
|
58
|
+
const timeoutID = setTimeout(() => {
|
|
59
|
+
timedOut = true;
|
|
60
|
+
controller.abort();
|
|
61
|
+
}, timeout);
|
|
62
|
+
const abortFromCaller = () => {
|
|
63
|
+
callerAborted = true;
|
|
64
|
+
controller.abort();
|
|
65
|
+
};
|
|
66
|
+
if (options.signal?.aborted) {
|
|
67
|
+
controller.abort();
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
options.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
71
|
+
}
|
|
54
72
|
try {
|
|
55
73
|
const headers = {
|
|
56
74
|
Accept: stream ? "text/event-stream" : "application/json",
|
|
@@ -80,12 +98,19 @@ export class HTTPClient {
|
|
|
80
98
|
throw error;
|
|
81
99
|
}
|
|
82
100
|
if (error instanceof Error && error.name === "AbortError") {
|
|
83
|
-
|
|
101
|
+
if (callerAborted || options.signal?.aborted) {
|
|
102
|
+
throw new APIConnectionError("Request aborted", error);
|
|
103
|
+
}
|
|
104
|
+
if (timedOut) {
|
|
105
|
+
throw new APIConnectionError(`Request timed out after ${timeout}ms`, error);
|
|
106
|
+
}
|
|
107
|
+
throw new APIConnectionError("Request aborted", error);
|
|
84
108
|
}
|
|
85
109
|
throw new APIConnectionError("Request failed", error);
|
|
86
110
|
}
|
|
87
111
|
finally {
|
|
88
112
|
clearTimeout(timeoutID);
|
|
113
|
+
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
89
114
|
}
|
|
90
115
|
}
|
|
91
116
|
}
|
|
@@ -99,6 +124,23 @@ function retryDelayMs(error, attempt) {
|
|
|
99
124
|
const jitter = Math.floor(Math.random() * 100);
|
|
100
125
|
return exponential + jitter;
|
|
101
126
|
}
|
|
102
|
-
function sleep(ms) {
|
|
103
|
-
|
|
127
|
+
function sleep(ms, signal) {
|
|
128
|
+
if (!signal) {
|
|
129
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
130
|
+
}
|
|
131
|
+
if (signal.aborted) {
|
|
132
|
+
return Promise.reject(new APIConnectionError("Request aborted"));
|
|
133
|
+
}
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const timeout = setTimeout(() => {
|
|
136
|
+
signal.removeEventListener("abort", abort);
|
|
137
|
+
resolve();
|
|
138
|
+
}, ms);
|
|
139
|
+
const abort = () => {
|
|
140
|
+
clearTimeout(timeout);
|
|
141
|
+
signal.removeEventListener("abort", abort);
|
|
142
|
+
reject(new APIConnectionError("Request aborted"));
|
|
143
|
+
};
|
|
144
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
145
|
+
});
|
|
104
146
|
}
|
package/dist/local/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { constants as fsConstants, watch as watchFS } from "node:fs";
|
|
3
|
-
import { access, copyFile, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, copyFile, lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { localSkillFromDirectory } from "../local-skills.js";
|
|
@@ -322,12 +322,15 @@ export class LocalFileStore {
|
|
|
322
322
|
if (item.type !== "file" || item.size > maxBytesPerFile || !isLikelyTextFile(item.path)) {
|
|
323
323
|
continue;
|
|
324
324
|
}
|
|
325
|
-
const raw = await
|
|
325
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
326
|
+
if (!raw) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
326
329
|
if (looksBinary(raw)) {
|
|
327
330
|
continue;
|
|
328
331
|
}
|
|
329
332
|
filesScanned++;
|
|
330
|
-
const lines = splitLines(raw.toString("utf8"));
|
|
333
|
+
const lines = splitLines(Buffer.from(raw).toString("utf8"));
|
|
331
334
|
for (let i = 0; i < lines.length; i++) {
|
|
332
335
|
if (!lines[i].includes(pattern)) {
|
|
333
336
|
continue;
|
|
@@ -363,7 +366,10 @@ export class LocalFileStore {
|
|
|
363
366
|
if (!isLikelyTextFile(item.path) || item.size > previewBytes * 4) {
|
|
364
367
|
continue;
|
|
365
368
|
}
|
|
366
|
-
const raw = await
|
|
369
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
370
|
+
if (!raw) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
367
373
|
if (looksBinary(raw)) {
|
|
368
374
|
continue;
|
|
369
375
|
}
|
|
@@ -371,7 +377,7 @@ export class LocalFileStore {
|
|
|
371
377
|
previews.push({
|
|
372
378
|
path: item.path,
|
|
373
379
|
size: item.size,
|
|
374
|
-
preview: raw.subarray(0, previewBytes).toString("utf8"),
|
|
380
|
+
preview: Buffer.from(raw.subarray(0, previewBytes)).toString("utf8"),
|
|
375
381
|
preview_truncated: truncated || undefined,
|
|
376
382
|
});
|
|
377
383
|
}
|
|
@@ -393,7 +399,10 @@ export class LocalFileStore {
|
|
|
393
399
|
if (ignored(relativePath, options.ignore)) {
|
|
394
400
|
continue;
|
|
395
401
|
}
|
|
396
|
-
const info = await
|
|
402
|
+
const info = await lstatOptional(fullPath);
|
|
403
|
+
if (!info) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
397
406
|
const item = {
|
|
398
407
|
path: relativePath,
|
|
399
408
|
fullPath,
|
|
@@ -401,7 +410,7 @@ export class LocalFileStore {
|
|
|
401
410
|
size: info.size,
|
|
402
411
|
modifiedAt: info.mtime,
|
|
403
412
|
};
|
|
404
|
-
if (
|
|
413
|
+
if (info.isDirectory()) {
|
|
405
414
|
if (options.includeDirectories) {
|
|
406
415
|
out.push(item);
|
|
407
416
|
}
|
|
@@ -813,6 +822,28 @@ async function fileExists(fullPath) {
|
|
|
813
822
|
return false;
|
|
814
823
|
}
|
|
815
824
|
}
|
|
825
|
+
async function lstatOptional(fullPath) {
|
|
826
|
+
try {
|
|
827
|
+
return await lstat(fullPath);
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function readOptionalFile(fullPath) {
|
|
837
|
+
try {
|
|
838
|
+
return await readFile(fullPath);
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR" || error?.code === "EISDIR") {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
816
847
|
function normalizeAppName(appName) {
|
|
817
848
|
const trimmed = appName.trim();
|
|
818
849
|
if (!trimmed) {
|
package/dist/types/common.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface ClientOptions {
|
|
|
20
20
|
export interface RequestOptions {
|
|
21
21
|
headers?: Record<string, string>;
|
|
22
22
|
timeout?: number;
|
|
23
|
+
/** Abort the underlying HTTP request or stream from caller-controlled UI/runtime state. */
|
|
24
|
+
signal?: AbortSignal;
|
|
23
25
|
/** Override automatic retries for this request. */
|
|
24
26
|
maxRetries?: number;
|
|
25
27
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.2.
|
|
2
|
-
export declare const USER_AGENT = "@agent-api/sdk/1.2.
|
|
1
|
+
export declare const VERSION = "1.2.3";
|
|
2
|
+
export declare const USER_AGENT = "@agent-api/sdk/1.2.3";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "1.2.
|
|
1
|
+
export const VERSION = "1.2.3";
|
|
2
2
|
export const USER_AGENT = `@agent-api/sdk/${VERSION}`;
|
|
@@ -36,6 +36,9 @@ class HTTPClient {
|
|
|
36
36
|
return await this.fetchOnce(method, path, body, options, stream, rawBody);
|
|
37
37
|
}
|
|
38
38
|
catch (error) {
|
|
39
|
+
if (options.signal?.aborted) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
39
42
|
if (!(error instanceof errors_js_1.APIError) || attempt >= maxRetries) {
|
|
40
43
|
throw error;
|
|
41
44
|
}
|
|
@@ -46,14 +49,29 @@ class HTTPClient {
|
|
|
46
49
|
}
|
|
47
50
|
attempt += 1;
|
|
48
51
|
const delayMs = retryDelayMs(error, attempt);
|
|
49
|
-
await sleep(delayMs);
|
|
52
|
+
await sleep(delayMs, options.signal);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
async fetchOnce(method, path, body, options, stream, rawBody) {
|
|
54
57
|
const controller = new AbortController();
|
|
55
58
|
const timeout = options.timeout ?? (stream ? this.options.streamTimeout : this.options.timeout);
|
|
56
|
-
|
|
59
|
+
let timedOut = false;
|
|
60
|
+
let callerAborted = options.signal?.aborted ?? false;
|
|
61
|
+
const timeoutID = setTimeout(() => {
|
|
62
|
+
timedOut = true;
|
|
63
|
+
controller.abort();
|
|
64
|
+
}, timeout);
|
|
65
|
+
const abortFromCaller = () => {
|
|
66
|
+
callerAborted = true;
|
|
67
|
+
controller.abort();
|
|
68
|
+
};
|
|
69
|
+
if (options.signal?.aborted) {
|
|
70
|
+
controller.abort();
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
options.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
74
|
+
}
|
|
57
75
|
try {
|
|
58
76
|
const headers = {
|
|
59
77
|
Accept: stream ? "text/event-stream" : "application/json",
|
|
@@ -83,12 +101,19 @@ class HTTPClient {
|
|
|
83
101
|
throw error;
|
|
84
102
|
}
|
|
85
103
|
if (error instanceof Error && error.name === "AbortError") {
|
|
86
|
-
|
|
104
|
+
if (callerAborted || options.signal?.aborted) {
|
|
105
|
+
throw new errors_js_1.APIConnectionError("Request aborted", error);
|
|
106
|
+
}
|
|
107
|
+
if (timedOut) {
|
|
108
|
+
throw new errors_js_1.APIConnectionError(`Request timed out after ${timeout}ms`, error);
|
|
109
|
+
}
|
|
110
|
+
throw new errors_js_1.APIConnectionError("Request aborted", error);
|
|
87
111
|
}
|
|
88
112
|
throw new errors_js_1.APIConnectionError("Request failed", error);
|
|
89
113
|
}
|
|
90
114
|
finally {
|
|
91
115
|
clearTimeout(timeoutID);
|
|
116
|
+
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
92
117
|
}
|
|
93
118
|
}
|
|
94
119
|
}
|
|
@@ -103,6 +128,23 @@ function retryDelayMs(error, attempt) {
|
|
|
103
128
|
const jitter = Math.floor(Math.random() * 100);
|
|
104
129
|
return exponential + jitter;
|
|
105
130
|
}
|
|
106
|
-
function sleep(ms) {
|
|
107
|
-
|
|
131
|
+
function sleep(ms, signal) {
|
|
132
|
+
if (!signal) {
|
|
133
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
134
|
+
}
|
|
135
|
+
if (signal.aborted) {
|
|
136
|
+
return Promise.reject(new errors_js_1.APIConnectionError("Request aborted"));
|
|
137
|
+
}
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const timeout = setTimeout(() => {
|
|
140
|
+
signal.removeEventListener("abort", abort);
|
|
141
|
+
resolve();
|
|
142
|
+
}, ms);
|
|
143
|
+
const abort = () => {
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
signal.removeEventListener("abort", abort);
|
|
146
|
+
reject(new errors_js_1.APIConnectionError("Request aborted"));
|
|
147
|
+
};
|
|
148
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
149
|
+
});
|
|
108
150
|
}
|
package/dist-cjs/local/core.js
CHANGED
|
@@ -337,12 +337,15 @@ class LocalFileStore {
|
|
|
337
337
|
if (item.type !== "file" || item.size > maxBytesPerFile || !isLikelyTextFile(item.path)) {
|
|
338
338
|
continue;
|
|
339
339
|
}
|
|
340
|
-
const raw = await (
|
|
340
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
341
|
+
if (!raw) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
341
344
|
if (looksBinary(raw)) {
|
|
342
345
|
continue;
|
|
343
346
|
}
|
|
344
347
|
filesScanned++;
|
|
345
|
-
const lines = splitLines(raw.toString("utf8"));
|
|
348
|
+
const lines = splitLines(Buffer.from(raw).toString("utf8"));
|
|
346
349
|
for (let i = 0; i < lines.length; i++) {
|
|
347
350
|
if (!lines[i].includes(pattern)) {
|
|
348
351
|
continue;
|
|
@@ -378,7 +381,10 @@ class LocalFileStore {
|
|
|
378
381
|
if (!isLikelyTextFile(item.path) || item.size > previewBytes * 4) {
|
|
379
382
|
continue;
|
|
380
383
|
}
|
|
381
|
-
const raw = await (
|
|
384
|
+
const raw = await readOptionalFile(item.fullPath);
|
|
385
|
+
if (!raw) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
382
388
|
if (looksBinary(raw)) {
|
|
383
389
|
continue;
|
|
384
390
|
}
|
|
@@ -386,7 +392,7 @@ class LocalFileStore {
|
|
|
386
392
|
previews.push({
|
|
387
393
|
path: item.path,
|
|
388
394
|
size: item.size,
|
|
389
|
-
preview: raw.subarray(0, previewBytes).toString("utf8"),
|
|
395
|
+
preview: Buffer.from(raw.subarray(0, previewBytes)).toString("utf8"),
|
|
390
396
|
preview_truncated: truncated || undefined,
|
|
391
397
|
});
|
|
392
398
|
}
|
|
@@ -408,7 +414,10 @@ class LocalFileStore {
|
|
|
408
414
|
if (ignored(relativePath, options.ignore)) {
|
|
409
415
|
continue;
|
|
410
416
|
}
|
|
411
|
-
const info = await (
|
|
417
|
+
const info = await lstatOptional(fullPath);
|
|
418
|
+
if (!info) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
412
421
|
const item = {
|
|
413
422
|
path: relativePath,
|
|
414
423
|
fullPath,
|
|
@@ -416,7 +425,7 @@ class LocalFileStore {
|
|
|
416
425
|
size: info.size,
|
|
417
426
|
modifiedAt: info.mtime,
|
|
418
427
|
};
|
|
419
|
-
if (
|
|
428
|
+
if (info.isDirectory()) {
|
|
420
429
|
if (options.includeDirectories) {
|
|
421
430
|
out.push(item);
|
|
422
431
|
}
|
|
@@ -833,6 +842,28 @@ async function fileExists(fullPath) {
|
|
|
833
842
|
return false;
|
|
834
843
|
}
|
|
835
844
|
}
|
|
845
|
+
async function lstatOptional(fullPath) {
|
|
846
|
+
try {
|
|
847
|
+
return await (0, promises_1.lstat)(fullPath);
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR") {
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async function readOptionalFile(fullPath) {
|
|
857
|
+
try {
|
|
858
|
+
return await (0, promises_1.readFile)(fullPath);
|
|
859
|
+
}
|
|
860
|
+
catch (error) {
|
|
861
|
+
if (error?.code === "ENOENT" || error?.code === "ENOTDIR" || error?.code === "EISDIR") {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
836
867
|
function normalizeAppName(appName) {
|
|
837
868
|
const trimmed = appName.trim();
|
|
838
869
|
if (!trimmed) {
|
package/dist-cjs/version.js
CHANGED