@copilotkit/runtime 1.50.1-next.3 → 1.50.2-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +6 -21
- package/dist/index.js +34 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/lib/integrations/node-http/index.ts +26 -94
- package/src/lib/integrations/node-http/request-handler.ts +111 -0
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
-
"version": "1.50.
|
|
12
|
+
"version": "1.50.2-next.0",
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"module": "./dist/index.mjs",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"rxjs": "7.8.1",
|
|
73
73
|
"type-graphql": "2.0.0-rc.1",
|
|
74
74
|
"zod": "^3.23.3",
|
|
75
|
-
"@copilotkit/shared": "1.50.
|
|
75
|
+
"@copilotkit/shared": "1.50.2-next.0"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"@anthropic-ai/sdk": "^0.57.0",
|
|
@@ -1,98 +1,17 @@
|
|
|
1
1
|
import { CreateCopilotRuntimeServerOptions, getCommonConfig } from "../shared";
|
|
2
2
|
import telemetry, { getRuntimeInstanceTelemetryInfo } from "../../telemetry-client";
|
|
3
3
|
import { createCopilotEndpointSingleRoute } from "@copilotkitnext/runtime";
|
|
4
|
-
import { IncomingMessage, ServerResponse } from "http";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const { done, value } = await reader.read();
|
|
16
|
-
if (done) {
|
|
17
|
-
this.push(null);
|
|
18
|
-
} else {
|
|
19
|
-
this.push(Buffer.from(value));
|
|
20
|
-
}
|
|
21
|
-
} catch (err) {
|
|
22
|
-
this.destroy(err as Error);
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getFullUrl(req: IncomingMessage): string {
|
|
29
|
-
const expressPath =
|
|
30
|
-
(req as any).originalUrl ??
|
|
31
|
-
((req as any).baseUrl ? `${(req as any).baseUrl}${req.url ?? ""}` : undefined);
|
|
32
|
-
const path = expressPath || req.url || "/";
|
|
33
|
-
const host =
|
|
34
|
-
(req.headers["x-forwarded-host"] as string) || (req.headers.host as string) || "localhost";
|
|
35
|
-
const proto =
|
|
36
|
-
(req.headers["x-forwarded-proto"] as string) ||
|
|
37
|
-
((req.socket as any).encrypted ? "https" : "http");
|
|
38
|
-
|
|
39
|
-
return `${proto}://${host}${path}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function toHeaders(rawHeaders: IncomingMessage["headers"]): Headers {
|
|
43
|
-
const headers = new Headers();
|
|
44
|
-
|
|
45
|
-
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
46
|
-
if (value === undefined) continue;
|
|
47
|
-
|
|
48
|
-
if (Array.isArray(value)) {
|
|
49
|
-
value.forEach((entry) => headers.append(key, entry));
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
headers.append(key, value);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return headers;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isStreamConsumed(req: IncomingWithBody): boolean {
|
|
60
|
-
const readableState = (req as any)._readableState;
|
|
61
|
-
|
|
62
|
-
return Boolean(
|
|
63
|
-
req.readableEnded || req.complete || readableState?.ended || readableState?.endEmitted,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function synthesizeBodyFromParsedBody(
|
|
68
|
-
parsedBody: unknown,
|
|
69
|
-
headers: Headers,
|
|
70
|
-
): { body: BodyInit | null; contentType?: string } {
|
|
71
|
-
if (parsedBody === null || parsedBody === undefined) {
|
|
72
|
-
return { body: null };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (parsedBody instanceof Buffer || parsedBody instanceof Uint8Array) {
|
|
76
|
-
return { body: parsedBody };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (typeof parsedBody === "string") {
|
|
80
|
-
return { body: parsedBody, contentType: headers.get("content-type") ?? "text/plain" };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
body: JSON.stringify(parsedBody),
|
|
85
|
-
contentType: "application/json",
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function isDisturbedOrLockedError(error: unknown): boolean {
|
|
90
|
-
return (
|
|
91
|
-
error instanceof TypeError &&
|
|
92
|
-
typeof error.message === "string" &&
|
|
93
|
-
(error.message.includes("disturbed") || error.message.includes("locked"))
|
|
94
|
-
);
|
|
95
|
-
}
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
5
|
+
import {
|
|
6
|
+
getFullUrl,
|
|
7
|
+
IncomingWithBody,
|
|
8
|
+
isDisturbedOrLockedError,
|
|
9
|
+
isStreamConsumed,
|
|
10
|
+
nodeStreamToReadableStream,
|
|
11
|
+
readableStreamToNodeStream,
|
|
12
|
+
synthesizeBodyFromParsedBody,
|
|
13
|
+
toHeaders,
|
|
14
|
+
} from "./request-handler";
|
|
96
15
|
|
|
97
16
|
export function copilotRuntimeNodeHttpEndpoint(options: CreateCopilotRuntimeServerOptions) {
|
|
98
17
|
const commonConfig = getCommonConfig(options);
|
|
@@ -124,7 +43,7 @@ export function copilotRuntimeNodeHttpEndpoint(options: CreateCopilotRuntimeServ
|
|
|
124
43
|
basePath: options.baseUrl ?? options.endpoint,
|
|
125
44
|
});
|
|
126
45
|
|
|
127
|
-
|
|
46
|
+
const handle = async function handler(req: IncomingWithBody, res: ServerResponse) {
|
|
128
47
|
const url = getFullUrl(req);
|
|
129
48
|
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
130
49
|
|
|
@@ -138,7 +57,7 @@ export function copilotRuntimeNodeHttpEndpoint(options: CreateCopilotRuntimeServ
|
|
|
138
57
|
let useDuplex = false;
|
|
139
58
|
|
|
140
59
|
if (hasBody && canStream) {
|
|
141
|
-
requestBody = req
|
|
60
|
+
requestBody = nodeStreamToReadableStream(req);
|
|
142
61
|
useDuplex = true;
|
|
143
62
|
}
|
|
144
63
|
|
|
@@ -208,4 +127,17 @@ export function copilotRuntimeNodeHttpEndpoint(options: CreateCopilotRuntimeServ
|
|
|
208
127
|
res.end();
|
|
209
128
|
}
|
|
210
129
|
};
|
|
130
|
+
|
|
131
|
+
return function (
|
|
132
|
+
reqOrRequest: IncomingMessage | Request,
|
|
133
|
+
res?: ServerResponse,
|
|
134
|
+
): Promise<void> | Promise<Response> | Response {
|
|
135
|
+
if (reqOrRequest instanceof Request) {
|
|
136
|
+
return honoApp.fetch(reqOrRequest as Request);
|
|
137
|
+
}
|
|
138
|
+
if (!res) {
|
|
139
|
+
throw new TypeError("ServerResponse is required for Node HTTP requests");
|
|
140
|
+
}
|
|
141
|
+
return handle(reqOrRequest as IncomingMessage, res);
|
|
142
|
+
};
|
|
211
143
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { IncomingMessage } from "http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
export type IncomingWithBody = IncomingMessage & { body?: unknown; complete?: boolean };
|
|
5
|
+
|
|
6
|
+
export function readableStreamToNodeStream(webStream: ReadableStream): Readable {
|
|
7
|
+
const reader = webStream.getReader();
|
|
8
|
+
|
|
9
|
+
return new Readable({
|
|
10
|
+
async read() {
|
|
11
|
+
try {
|
|
12
|
+
const { done, value } = await reader.read();
|
|
13
|
+
if (done) {
|
|
14
|
+
this.push(null);
|
|
15
|
+
} else {
|
|
16
|
+
this.push(Buffer.from(value));
|
|
17
|
+
}
|
|
18
|
+
} catch (err) {
|
|
19
|
+
this.destroy(err as Error);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function nodeStreamToReadableStream(nodeStream: Readable): ReadableStream<Uint8Array> {
|
|
26
|
+
return new ReadableStream({
|
|
27
|
+
start(controller) {
|
|
28
|
+
nodeStream.on("data", (chunk) => {
|
|
29
|
+
controller.enqueue(chunk instanceof Buffer ? new Uint8Array(chunk) : chunk);
|
|
30
|
+
});
|
|
31
|
+
nodeStream.on("end", () => {
|
|
32
|
+
controller.close();
|
|
33
|
+
});
|
|
34
|
+
nodeStream.on("error", (err) => {
|
|
35
|
+
controller.error(err);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
cancel() {
|
|
39
|
+
nodeStream.destroy();
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getFullUrl(req: IncomingMessage): string {
|
|
45
|
+
// Use req.url (path relative to mount point) for Hono routing to work correctly.
|
|
46
|
+
// Express sets req.url to the path after the mount point (e.g., "/" when mounted at "/copilotkit").
|
|
47
|
+
// Pure Node HTTP sets req.url to the full path.
|
|
48
|
+
const path = req.url || "/";
|
|
49
|
+
const host =
|
|
50
|
+
(req.headers["x-forwarded-host"] as string) || (req.headers.host as string) || "localhost";
|
|
51
|
+
const proto =
|
|
52
|
+
(req.headers["x-forwarded-proto"] as string) ||
|
|
53
|
+
((req.socket as any).encrypted ? "https" : "http");
|
|
54
|
+
|
|
55
|
+
return `${proto}://${host}${path}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function toHeaders(rawHeaders: IncomingMessage["headers"]): Headers {
|
|
59
|
+
const headers = new Headers();
|
|
60
|
+
|
|
61
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
62
|
+
if (value === undefined) continue;
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
value.forEach((entry) => headers.append(key, entry));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
headers.append(key, value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return headers;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isStreamConsumed(req: IncomingWithBody): boolean {
|
|
76
|
+
const readableState = (req as any)._readableState;
|
|
77
|
+
|
|
78
|
+
return Boolean(
|
|
79
|
+
req.readableEnded || req.complete || readableState?.ended || readableState?.endEmitted,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function synthesizeBodyFromParsedBody(
|
|
84
|
+
parsedBody: unknown,
|
|
85
|
+
headers: Headers,
|
|
86
|
+
): { body: BodyInit | null; contentType?: string } {
|
|
87
|
+
if (parsedBody === null || parsedBody === undefined) {
|
|
88
|
+
return { body: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (parsedBody instanceof Buffer || parsedBody instanceof Uint8Array) {
|
|
92
|
+
return { body: parsedBody };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof parsedBody === "string") {
|
|
96
|
+
return { body: parsedBody, contentType: headers.get("content-type") ?? "text/plain" };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
body: JSON.stringify(parsedBody),
|
|
101
|
+
contentType: "application/json",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function isDisturbedOrLockedError(error: unknown): boolean {
|
|
106
|
+
return (
|
|
107
|
+
error instanceof TypeError &&
|
|
108
|
+
typeof error.message === "string" &&
|
|
109
|
+
(error.message.includes("disturbed") || error.message.includes("locked"))
|
|
110
|
+
);
|
|
111
|
+
}
|