@nextclaw/ncp-http-agent-client 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +33 -31
- package/dist/index.js +351 -415
- package/package.json +3 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
import { NcpAgentClientEndpoint,
|
|
1
|
+
import { NcpAgentClientEndpoint, NcpEndpointEvent, NcpEndpointManifest, NcpEndpointSubscriber, NcpMessageAbortPayload, NcpRequestEnvelope, NcpStreamRequestPayload } from "@nextclaw/ncp";
|
|
2
2
|
|
|
3
|
+
//#region src/utils.d.ts
|
|
3
4
|
type FetchLike = (input: URL | string | Request, init?: RequestInit) => Promise<Response>;
|
|
4
|
-
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/client.d.ts
|
|
5
7
|
type NcpHttpAgentClientOptions = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
basePath?: string;
|
|
10
|
+
endpointId?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
fetchImpl?: FetchLike;
|
|
11
13
|
};
|
|
12
14
|
declare class NcpHttpAgentClientEndpoint implements NcpAgentClientEndpoint {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
readonly manifest: NcpEndpointManifest;
|
|
16
|
+
private readonly baseUrl;
|
|
17
|
+
private readonly basePath;
|
|
18
|
+
private readonly fetchImpl;
|
|
19
|
+
private readonly defaultHeaders;
|
|
20
|
+
private readonly subscribers;
|
|
21
|
+
private readonly activeControllers;
|
|
22
|
+
private started;
|
|
23
|
+
constructor(options: NcpHttpAgentClientOptions);
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
emit(event: NcpEndpointEvent): Promise<void>;
|
|
27
|
+
subscribe(listener: NcpEndpointSubscriber): () => void;
|
|
28
|
+
send(envelope: NcpRequestEnvelope): Promise<void>;
|
|
29
|
+
stream(payload: NcpStreamRequestPayload): Promise<void>;
|
|
30
|
+
abort(payload: NcpMessageAbortPayload): Promise<void>;
|
|
31
|
+
private ensureStarted;
|
|
32
|
+
private publish;
|
|
33
|
+
private resolveUrl;
|
|
34
|
+
private jsonRequest;
|
|
35
|
+
private streamRequest;
|
|
36
|
+
private handleSseFrame;
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
export { NcpHttpAgentClientEndpoint, type NcpHttpAgentClientOptions };
|
|
38
|
+
//#endregion
|
|
39
|
+
export { NcpHttpAgentClientEndpoint, type NcpHttpAgentClientOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,453 +1,389 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
NcpEventType
|
|
4
|
-
} from "@nextclaw/ncp";
|
|
5
|
-
|
|
6
|
-
// src/sse.ts
|
|
1
|
+
import { NcpEventType } from "@nextclaw/ncp";
|
|
2
|
+
//#region src/sse.ts
|
|
7
3
|
function parseSseFrame(frameText) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
return {
|
|
28
|
-
event: eventName,
|
|
29
|
-
data: dataLines.join("\n")
|
|
30
|
-
};
|
|
4
|
+
const lines = frameText.split(/\r?\n/);
|
|
5
|
+
let eventName = "message";
|
|
6
|
+
const dataLines = [];
|
|
7
|
+
for (const rawLine of lines) {
|
|
8
|
+
const line = rawLine.trimEnd();
|
|
9
|
+
if (!line || line.startsWith(":")) continue;
|
|
10
|
+
if (line.startsWith("event:")) {
|
|
11
|
+
eventName = line.slice(6).trim() || "message";
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
|
15
|
+
}
|
|
16
|
+
if (dataLines.length === 0) return null;
|
|
17
|
+
return {
|
|
18
|
+
event: eventName,
|
|
19
|
+
data: dataLines.join("\n")
|
|
20
|
+
};
|
|
31
21
|
}
|
|
32
22
|
async function* consumeSseStream(stream) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
for (const frame of frames) {
|
|
52
|
-
yield frame;
|
|
53
|
-
}
|
|
54
|
-
} finally {
|
|
55
|
-
reader.releaseLock();
|
|
56
|
-
}
|
|
23
|
+
const reader = stream.getReader();
|
|
24
|
+
const decoder = new TextDecoder();
|
|
25
|
+
let buffer = "";
|
|
26
|
+
try {
|
|
27
|
+
while (true) {
|
|
28
|
+
const { value, done } = await reader.read();
|
|
29
|
+
if (done) break;
|
|
30
|
+
buffer += decoder.decode(value, { stream: true });
|
|
31
|
+
const { frames, rest } = drainFrames(buffer);
|
|
32
|
+
buffer = rest;
|
|
33
|
+
for (const frame of frames) yield frame;
|
|
34
|
+
}
|
|
35
|
+
buffer += decoder.decode();
|
|
36
|
+
const { frames } = drainFrames(buffer, true);
|
|
37
|
+
for (const frame of frames) yield frame;
|
|
38
|
+
} finally {
|
|
39
|
+
reader.releaseLock();
|
|
40
|
+
}
|
|
57
41
|
}
|
|
58
42
|
function drainFrames(rawBuffer, flush = false) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
43
|
+
const parts = rawBuffer.split(/\r?\n\r?\n/);
|
|
44
|
+
const rest = parts.pop() ?? "";
|
|
45
|
+
const frames = [];
|
|
46
|
+
for (const part of parts) {
|
|
47
|
+
const frame = parseSseFrame(part);
|
|
48
|
+
if (frame) frames.push(frame);
|
|
49
|
+
}
|
|
50
|
+
if (flush && rest.trim()) {
|
|
51
|
+
const frame = parseSseFrame(rest);
|
|
52
|
+
if (frame) frames.push(frame);
|
|
53
|
+
return {
|
|
54
|
+
frames,
|
|
55
|
+
rest: ""
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
frames,
|
|
60
|
+
rest
|
|
61
|
+
};
|
|
76
62
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
var DEFAULT_ENDPOINT_ID = "ncp-http-agent-client";
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/utils.ts
|
|
65
|
+
const DEFAULT_BASE_PATH = "/ncp/agent";
|
|
81
66
|
var NcpHttpAgentClientError = class extends Error {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
ncpError;
|
|
68
|
+
alreadyPublished;
|
|
69
|
+
constructor(ncpError, alreadyPublished = false) {
|
|
70
|
+
super(ncpError.message);
|
|
71
|
+
this.name = `NcpHttpAgentClientError(${ncpError.code})`;
|
|
72
|
+
this.ncpError = ncpError;
|
|
73
|
+
this.alreadyPublished = alreadyPublished;
|
|
74
|
+
}
|
|
90
75
|
};
|
|
91
76
|
function toBaseUrl(baseUrl) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return new URL(trimmed);
|
|
77
|
+
const trimmed = baseUrl.trim();
|
|
78
|
+
if (!trimmed) throw new Error("NcpHttpAgentClient requires a non-empty baseUrl.");
|
|
79
|
+
return new URL(trimmed);
|
|
97
80
|
}
|
|
98
81
|
function resolveFetchImpl(fetchImpl) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (typeof globalThis.fetch === "function") {
|
|
103
|
-
return globalThis.fetch.bind(globalThis);
|
|
104
|
-
}
|
|
105
|
-
throw new Error("No fetch implementation found. Pass options.fetchImpl explicitly.");
|
|
82
|
+
if (fetchImpl) return fetchImpl;
|
|
83
|
+
if (typeof globalThis.fetch === "function") return globalThis.fetch.bind(globalThis);
|
|
84
|
+
throw new Error("No fetch implementation found. Pass options.fetchImpl explicitly.");
|
|
106
85
|
}
|
|
107
86
|
function normalizeBasePath(basePath) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
113
|
-
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
87
|
+
const raw = (basePath ?? "/ncp/agent").trim();
|
|
88
|
+
if (!raw) return DEFAULT_BASE_PATH;
|
|
89
|
+
const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
|
|
90
|
+
return withSlash.endsWith("/") ? withSlash.slice(0, -1) : withSlash;
|
|
114
91
|
}
|
|
115
92
|
async function safeReadText(response) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
93
|
+
try {
|
|
94
|
+
return (await response.text()).trim();
|
|
95
|
+
} catch {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
122
98
|
}
|
|
123
99
|
function toNcpError(error) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
code: normalizeErrorCode(void 0),
|
|
133
|
-
message,
|
|
134
|
-
...error instanceof Error && error.stack ? { details: { stack: error.stack } } : {}
|
|
135
|
-
};
|
|
100
|
+
if (isNcpHttpAgentClientError(error)) return error.ncpError;
|
|
101
|
+
if (isNcpError(error)) return error;
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
|
|
103
|
+
return {
|
|
104
|
+
code: normalizeErrorCode(void 0),
|
|
105
|
+
message,
|
|
106
|
+
...error instanceof Error && error.stack ? { details: { stack: error.stack } } : {}
|
|
107
|
+
};
|
|
136
108
|
}
|
|
137
109
|
function ncpErrorToError(error, options = {}) {
|
|
138
|
-
|
|
110
|
+
return new NcpHttpAgentClientError(error, options.alreadyPublished ?? false);
|
|
139
111
|
}
|
|
140
112
|
function isNcpError(value) {
|
|
141
|
-
|
|
113
|
+
return isRecord(value) && typeof value.code === "string" && typeof value.message === "string";
|
|
142
114
|
}
|
|
143
115
|
function isRecord(value) {
|
|
144
|
-
|
|
116
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
145
117
|
}
|
|
146
118
|
function isNcpHttpAgentClientError(error) {
|
|
147
|
-
|
|
119
|
+
return error instanceof NcpHttpAgentClientError;
|
|
148
120
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
121
|
+
const ERROR_CODE_MAP = {
|
|
122
|
+
"config-error": "config-error",
|
|
123
|
+
"auth-error": "auth-error",
|
|
124
|
+
"runtime-error": "runtime-error",
|
|
125
|
+
"timeout-error": "timeout-error",
|
|
126
|
+
"abort-error": "abort-error"
|
|
155
127
|
};
|
|
156
128
|
function normalizeErrorCode(code) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (lowered.includes("abort") || lowered.includes("cancel")) return "abort-error";
|
|
167
|
-
if (lowered.includes("auth")) return "auth-error";
|
|
168
|
-
if (lowered.includes("config")) return "config-error";
|
|
169
|
-
return "runtime-error";
|
|
129
|
+
if (!code) return "runtime-error";
|
|
130
|
+
const mapped = ERROR_CODE_MAP[code];
|
|
131
|
+
if (mapped) return mapped;
|
|
132
|
+
const lowered = code.toLowerCase();
|
|
133
|
+
if (lowered.includes("timeout")) return "timeout-error";
|
|
134
|
+
if (lowered.includes("abort") || lowered.includes("cancel")) return "abort-error";
|
|
135
|
+
if (lowered.includes("auth")) return "auth-error";
|
|
136
|
+
if (lowered.includes("config")) return "config-error";
|
|
137
|
+
return "runtime-error";
|
|
170
138
|
}
|
|
171
|
-
|
|
172
|
-
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/parsers.ts
|
|
173
141
|
function parseNcpEvent(rawData) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (typeof parsed.type !== "string" || !parsed.type.trim()) {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
return parsed;
|
|
142
|
+
const parsed = parseJsonRecord(rawData);
|
|
143
|
+
if (!parsed) return null;
|
|
144
|
+
if (typeof parsed.type !== "string" || !parsed.type.trim()) return null;
|
|
145
|
+
return parsed;
|
|
182
146
|
}
|
|
183
147
|
function parseNcpError(rawData) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
code: normalizedCode,
|
|
199
|
-
message: typeof parsed.message === "string" && parsed.message ? parsed.message : "Unknown stream error.",
|
|
200
|
-
...Object.keys(details).length > 0 ? { details } : {}
|
|
201
|
-
};
|
|
148
|
+
const parsed = parseJsonRecord(rawData);
|
|
149
|
+
if (!parsed) return {
|
|
150
|
+
code: "runtime-error",
|
|
151
|
+
message: rawData || "Unknown stream error."
|
|
152
|
+
};
|
|
153
|
+
const inputCode = typeof parsed.code === "string" ? parsed.code : void 0;
|
|
154
|
+
const normalizedCode = normalizeErrorCode(inputCode);
|
|
155
|
+
const details = isRecord(parsed.details) ? parsed.details : {};
|
|
156
|
+
if (inputCode && inputCode !== normalizedCode) details.originalCode = inputCode;
|
|
157
|
+
return {
|
|
158
|
+
code: normalizedCode,
|
|
159
|
+
message: typeof parsed.message === "string" && parsed.message ? parsed.message : "Unknown stream error.",
|
|
160
|
+
...Object.keys(details).length > 0 ? { details } : {}
|
|
161
|
+
};
|
|
202
162
|
}
|
|
203
163
|
function parseJsonRecord(value) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(value);
|
|
166
|
+
return isRecord(parsed) ? parsed : null;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
210
170
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/client.ts
|
|
173
|
+
const SUPPORTED_PART_TYPES = [
|
|
174
|
+
"text",
|
|
175
|
+
"file",
|
|
176
|
+
"source",
|
|
177
|
+
"step-start",
|
|
178
|
+
"reasoning",
|
|
179
|
+
"tool-invocation",
|
|
180
|
+
"card",
|
|
181
|
+
"rich-text",
|
|
182
|
+
"action",
|
|
183
|
+
"extension"
|
|
224
184
|
];
|
|
225
185
|
var NcpHttpAgentClientEndpoint = class {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
handleSseFrame(frame) {
|
|
429
|
-
if (frame.event === "ncp-event") {
|
|
430
|
-
const event = parseNcpEvent(frame.data);
|
|
431
|
-
if (!event) {
|
|
432
|
-
this.publish({
|
|
433
|
-
type: NcpEventType.EndpointError,
|
|
434
|
-
payload: {
|
|
435
|
-
code: "runtime-error",
|
|
436
|
-
message: "Received malformed ncp-event frame."
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
this.publish(event);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (frame.event === "error") {
|
|
445
|
-
const ncpError = parseNcpError(frame.data);
|
|
446
|
-
this.publish({ type: NcpEventType.EndpointError, payload: ncpError });
|
|
447
|
-
throw ncpErrorToError(ncpError, { alreadyPublished: true });
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
export {
|
|
452
|
-
NcpHttpAgentClientEndpoint
|
|
186
|
+
manifest;
|
|
187
|
+
baseUrl;
|
|
188
|
+
basePath;
|
|
189
|
+
fetchImpl;
|
|
190
|
+
defaultHeaders;
|
|
191
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
192
|
+
activeControllers = /* @__PURE__ */ new Set();
|
|
193
|
+
started = false;
|
|
194
|
+
constructor(options) {
|
|
195
|
+
this.baseUrl = toBaseUrl(options.baseUrl);
|
|
196
|
+
this.basePath = normalizeBasePath(options.basePath);
|
|
197
|
+
this.fetchImpl = resolveFetchImpl(options.fetchImpl);
|
|
198
|
+
this.defaultHeaders = options.headers ?? {};
|
|
199
|
+
this.manifest = {
|
|
200
|
+
endpointKind: "custom",
|
|
201
|
+
endpointId: options.endpointId?.trim() || "ncp-http-agent-client",
|
|
202
|
+
version: "0.1.0",
|
|
203
|
+
supportsStreaming: true,
|
|
204
|
+
supportsAbort: true,
|
|
205
|
+
supportsProactiveMessages: false,
|
|
206
|
+
supportsLiveSessionStream: true,
|
|
207
|
+
supportedPartTypes: SUPPORTED_PART_TYPES,
|
|
208
|
+
expectedLatency: "seconds",
|
|
209
|
+
metadata: {
|
|
210
|
+
transport: "http+sse",
|
|
211
|
+
scope: "agent"
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
async start() {
|
|
216
|
+
if (this.started) return;
|
|
217
|
+
this.started = true;
|
|
218
|
+
this.publish({ type: NcpEventType.EndpointReady });
|
|
219
|
+
}
|
|
220
|
+
async stop() {
|
|
221
|
+
if (!this.started) return;
|
|
222
|
+
this.started = false;
|
|
223
|
+
for (const controller of this.activeControllers) controller.abort();
|
|
224
|
+
this.activeControllers.clear();
|
|
225
|
+
}
|
|
226
|
+
async emit(event) {
|
|
227
|
+
switch (event.type) {
|
|
228
|
+
case "message.request":
|
|
229
|
+
await this.send(event.payload);
|
|
230
|
+
return;
|
|
231
|
+
case "message.stream-request":
|
|
232
|
+
await this.stream(event.payload);
|
|
233
|
+
return;
|
|
234
|
+
case "message.abort":
|
|
235
|
+
await this.abort(event.payload);
|
|
236
|
+
return;
|
|
237
|
+
default:
|
|
238
|
+
this.publish(event);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
subscribe(listener) {
|
|
243
|
+
this.subscribers.add(listener);
|
|
244
|
+
return () => {
|
|
245
|
+
this.subscribers.delete(listener);
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async send(envelope) {
|
|
249
|
+
await this.ensureStarted();
|
|
250
|
+
await this.jsonRequest({
|
|
251
|
+
path: "/send",
|
|
252
|
+
method: "POST",
|
|
253
|
+
body: envelope
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async stream(payload) {
|
|
257
|
+
await this.ensureStarted();
|
|
258
|
+
const query = new URLSearchParams({ sessionId: payload.sessionId });
|
|
259
|
+
await this.streamRequest({
|
|
260
|
+
path: `/stream?${query.toString()}`,
|
|
261
|
+
method: "GET"
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async abort(payload) {
|
|
265
|
+
await this.ensureStarted();
|
|
266
|
+
const controller = new AbortController();
|
|
267
|
+
this.activeControllers.add(controller);
|
|
268
|
+
try {
|
|
269
|
+
const response = await this.fetchImpl(this.resolveUrl("/abort"), {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: {
|
|
272
|
+
...this.defaultHeaders,
|
|
273
|
+
"content-type": "application/json",
|
|
274
|
+
accept: "application/json"
|
|
275
|
+
},
|
|
276
|
+
body: JSON.stringify(payload),
|
|
277
|
+
signal: controller.signal
|
|
278
|
+
});
|
|
279
|
+
if (!response.ok) throw new Error(`Abort request failed with HTTP ${response.status}: ${await safeReadText(response)}`);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (controller.signal.aborted) return;
|
|
282
|
+
const ncpError = toNcpError(error);
|
|
283
|
+
this.publish({
|
|
284
|
+
type: NcpEventType.EndpointError,
|
|
285
|
+
payload: ncpError
|
|
286
|
+
});
|
|
287
|
+
throw ncpErrorToError(ncpError);
|
|
288
|
+
} finally {
|
|
289
|
+
this.activeControllers.delete(controller);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async ensureStarted() {
|
|
293
|
+
if (!this.started) await this.start();
|
|
294
|
+
}
|
|
295
|
+
publish(event) {
|
|
296
|
+
for (const subscriber of this.subscribers) subscriber(event);
|
|
297
|
+
}
|
|
298
|
+
resolveUrl(path) {
|
|
299
|
+
return new URL(`${this.basePath}${path}`, this.baseUrl);
|
|
300
|
+
}
|
|
301
|
+
async jsonRequest(options) {
|
|
302
|
+
const controller = new AbortController();
|
|
303
|
+
this.activeControllers.add(controller);
|
|
304
|
+
try {
|
|
305
|
+
const response = await this.fetchImpl(this.resolveUrl(options.path), {
|
|
306
|
+
method: options.method,
|
|
307
|
+
headers: {
|
|
308
|
+
...this.defaultHeaders,
|
|
309
|
+
"content-type": "application/json",
|
|
310
|
+
accept: "application/json"
|
|
311
|
+
},
|
|
312
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
313
|
+
signal: controller.signal
|
|
314
|
+
});
|
|
315
|
+
if (!response.ok) throw new Error(`NCP request failed with HTTP ${response.status}: ${await safeReadText(response)}`);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (controller.signal.aborted) return;
|
|
318
|
+
if (isNcpHttpAgentClientError(error)) throw error;
|
|
319
|
+
const ncpError = toNcpError(error);
|
|
320
|
+
this.publish({
|
|
321
|
+
type: NcpEventType.EndpointError,
|
|
322
|
+
payload: ncpError
|
|
323
|
+
});
|
|
324
|
+
throw ncpErrorToError(ncpError);
|
|
325
|
+
} finally {
|
|
326
|
+
this.activeControllers.delete(controller);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async streamRequest(options) {
|
|
330
|
+
const controller = new AbortController();
|
|
331
|
+
this.activeControllers.add(controller);
|
|
332
|
+
try {
|
|
333
|
+
const response = await this.fetchImpl(this.resolveUrl(options.path), {
|
|
334
|
+
method: options.method,
|
|
335
|
+
headers: {
|
|
336
|
+
...this.defaultHeaders,
|
|
337
|
+
accept: "text/event-stream",
|
|
338
|
+
...options.body !== void 0 ? { "content-type": "application/json" } : {}
|
|
339
|
+
},
|
|
340
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
341
|
+
signal: controller.signal
|
|
342
|
+
});
|
|
343
|
+
if (!response.ok) throw new Error(`NCP stream request failed with HTTP ${response.status}: ${await safeReadText(response)}`);
|
|
344
|
+
if (!response.body) throw new Error("NCP stream response has no body.");
|
|
345
|
+
for await (const frame of consumeSseStream(response.body)) {
|
|
346
|
+
if (controller.signal.aborted) return;
|
|
347
|
+
this.handleSseFrame(frame);
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (controller.signal.aborted) return;
|
|
351
|
+
if (isNcpHttpAgentClientError(error)) throw error;
|
|
352
|
+
const ncpError = toNcpError(error);
|
|
353
|
+
this.publish({
|
|
354
|
+
type: NcpEventType.EndpointError,
|
|
355
|
+
payload: ncpError
|
|
356
|
+
});
|
|
357
|
+
throw ncpErrorToError(ncpError);
|
|
358
|
+
} finally {
|
|
359
|
+
this.activeControllers.delete(controller);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
handleSseFrame(frame) {
|
|
363
|
+
if (frame.event === "ncp-event") {
|
|
364
|
+
const event = parseNcpEvent(frame.data);
|
|
365
|
+
if (!event) {
|
|
366
|
+
this.publish({
|
|
367
|
+
type: NcpEventType.EndpointError,
|
|
368
|
+
payload: {
|
|
369
|
+
code: "runtime-error",
|
|
370
|
+
message: "Received malformed ncp-event frame."
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this.publish(event);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (frame.event === "error") {
|
|
379
|
+
const ncpError = parseNcpError(frame.data);
|
|
380
|
+
this.publish({
|
|
381
|
+
type: NcpEventType.EndpointError,
|
|
382
|
+
payload: ncpError
|
|
383
|
+
});
|
|
384
|
+
throw ncpErrorToError(ncpError, { alreadyPublished: true });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
453
387
|
};
|
|
388
|
+
//#endregion
|
|
389
|
+
export { NcpHttpAgentClientEndpoint };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ncp-http-agent-client",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "HTTP/SSE client transport adapter for NCP agent endpoints.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,17 +15,16 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@nextclaw/ncp": "0.5.
|
|
18
|
+
"@nextclaw/ncp": "0.5.2"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.17.6",
|
|
22
22
|
"prettier": "^3.3.3",
|
|
23
|
-
"tsup": "^8.3.5",
|
|
24
23
|
"typescript": "^5.6.3",
|
|
25
24
|
"vitest": "^4.1.2"
|
|
26
25
|
},
|
|
27
26
|
"scripts": {
|
|
28
|
-
"build": "
|
|
27
|
+
"build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension",
|
|
29
28
|
"lint": "eslint .",
|
|
30
29
|
"tsc": "tsc -p tsconfig.json",
|
|
31
30
|
"test": "vitest run"
|