@copilotkitnext/runtime 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.cursor/rules/runtime.always.mdc +9 -0
  2. package/.turbo/turbo-build.log +22 -0
  3. package/.turbo/turbo-check-types.log +4 -0
  4. package/.turbo/turbo-lint.log +56 -0
  5. package/.turbo/turbo-test$colon$coverage.log +149 -0
  6. package/.turbo/turbo-test.log +107 -0
  7. package/LICENSE +11 -0
  8. package/README-RUNNERS.md +78 -0
  9. package/dist/index.d.mts +245 -0
  10. package/dist/index.d.ts +245 -0
  11. package/dist/index.js +1873 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/index.mjs +1841 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/eslint.config.mjs +3 -0
  16. package/package.json +62 -0
  17. package/src/__tests__/get-runtime-info.test.ts +117 -0
  18. package/src/__tests__/handle-run.test.ts +69 -0
  19. package/src/__tests__/handle-transcribe.test.ts +289 -0
  20. package/src/__tests__/in-process-agent-runner-messages.test.ts +599 -0
  21. package/src/__tests__/in-process-agent-runner.test.ts +726 -0
  22. package/src/__tests__/middleware.test.ts +432 -0
  23. package/src/__tests__/routing.test.ts +257 -0
  24. package/src/endpoint.ts +150 -0
  25. package/src/handler.ts +3 -0
  26. package/src/handlers/get-runtime-info.ts +50 -0
  27. package/src/handlers/handle-connect.ts +144 -0
  28. package/src/handlers/handle-run.ts +156 -0
  29. package/src/handlers/handle-transcribe.ts +126 -0
  30. package/src/index.ts +8 -0
  31. package/src/middleware.ts +232 -0
  32. package/src/runner/__tests__/enterprise-runner.test.ts +992 -0
  33. package/src/runner/__tests__/event-compaction.test.ts +253 -0
  34. package/src/runner/__tests__/in-memory-runner.test.ts +483 -0
  35. package/src/runner/__tests__/sqlite-runner.test.ts +975 -0
  36. package/src/runner/agent-runner.ts +27 -0
  37. package/src/runner/enterprise.ts +653 -0
  38. package/src/runner/event-compaction.ts +250 -0
  39. package/src/runner/in-memory.ts +322 -0
  40. package/src/runner/index.ts +0 -0
  41. package/src/runner/sqlite.ts +481 -0
  42. package/src/runtime.ts +53 -0
  43. package/src/transcription-service/transcription-service-openai.ts +29 -0
  44. package/src/transcription-service/transcription-service.ts +11 -0
  45. package/tsconfig.json +13 -0
  46. package/tsup.config.ts +11 -0
  47. package/vitest.config.mjs +15 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Middleware support for CopilotKit Runtime.
3
+ *
4
+ * A middleware hook can be provided as either:
5
+ * 1. A **callback function** executed in-process.
6
+ * 2. A **webhook URL** (http/https). The runtime will `POST` a JSON payload
7
+ * to the URL and, for *before* hooks, accept an optional modified
8
+ * `Request` object in the response body.
9
+ *
10
+ * Two lifecycle hooks are available:
11
+ * • `BEFORE_REQUEST` – runs *before* the request handler.
12
+ * • `AFTER_REQUEST` – runs *after* the handler returns a `Response`.
13
+ */
14
+
15
+ import type { CopilotRuntime } from "./runtime";
16
+ import type { MaybePromise } from "@copilotkitnext/shared";
17
+ import { logger } from "@copilotkitnext/shared";
18
+
19
+ /* ------------------------------------------------------------------------------------------------
20
+ * Public types
21
+ * --------------------------------------------------------------------------------------------- */
22
+
23
+ /** A string beginning with http:// or https:// that points to a webhook endpoint. */
24
+ export type MiddlewareURL = `${"http" | "https"}://${string}`;
25
+
26
+ export interface BeforeRequestMiddlewareParameters {
27
+ runtime: CopilotRuntime;
28
+ request: Request;
29
+ path: string;
30
+ }
31
+ export interface AfterRequestMiddlewareParameters {
32
+ runtime: CopilotRuntime;
33
+ response: Response;
34
+ path: string;
35
+ }
36
+
37
+ export type BeforeRequestMiddlewareFn = (
38
+ params: BeforeRequestMiddlewareParameters
39
+ ) => MaybePromise<Request | void>;
40
+ export type AfterRequestMiddlewareFn = (
41
+ params: AfterRequestMiddlewareParameters
42
+ ) => MaybePromise<void>;
43
+
44
+ /**
45
+ * A middleware value can be either a callback function or a webhook URL.
46
+ */
47
+ export type BeforeRequestMiddleware = BeforeRequestMiddlewareFn | MiddlewareURL;
48
+ export type AfterRequestMiddleware = AfterRequestMiddlewareFn | MiddlewareURL;
49
+
50
+ /** Lifecycle events emitted to webhook middleware. */
51
+ export enum CopilotKitMiddlewareEvent {
52
+ BeforeRequest = "BEFORE_REQUEST",
53
+ AfterRequest = "AFTER_REQUEST",
54
+ }
55
+
56
+ /** Stages used by the Middleware Webhook Protocol */
57
+ /** Stages used by the CopilotKit webhook protocol */
58
+ export enum WebhookStage {
59
+ BeforeRequest = "before_request",
60
+ AfterRequest = "after_request",
61
+ }
62
+
63
+ /* ------------------------------------------------------------------------------------------------
64
+ * Internal helpers – (de)serialisation
65
+ * --------------------------------------------------------------------------------------------- */
66
+
67
+ function isMiddlewareURL(value: unknown): value is MiddlewareURL {
68
+ return typeof value === "string" && /^https?:\/\//.test(value);
69
+ }
70
+
71
+ export async function callBeforeRequestMiddleware({
72
+ runtime,
73
+ request,
74
+ path,
75
+ }: BeforeRequestMiddlewareParameters): Promise<Request | void> {
76
+ const mw = runtime.beforeRequestMiddleware;
77
+ if (!mw) return;
78
+
79
+ // Function-based middleware (in-process)
80
+ if (typeof mw === "function") {
81
+ return (mw as BeforeRequestMiddlewareFn)({ runtime, request, path });
82
+ }
83
+
84
+ // Webhook middleware
85
+ if (isMiddlewareURL(mw)) {
86
+ const clone = request.clone();
87
+ const url = new URL(request.url);
88
+ const headersObj: Record<string, string> = {};
89
+ clone.headers.forEach((v, k) => {
90
+ headersObj[k] = v;
91
+ });
92
+ let bodyJson: unknown = undefined;
93
+ try {
94
+ bodyJson = await clone.json();
95
+ } catch {
96
+ /* ignore */
97
+ }
98
+
99
+ const payload = {
100
+ method: request.method,
101
+ path: url.pathname,
102
+ query: url.search.startsWith("?") ? url.search.slice(1) : url.search,
103
+ headers: headersObj,
104
+ body: bodyJson,
105
+ };
106
+
107
+ const ac = new AbortController();
108
+ const to = setTimeout(() => ac.abort(), 2000);
109
+ let res: Response;
110
+ try {
111
+ res = await fetch(mw, {
112
+ method: "POST",
113
+ headers: {
114
+ "content-type": "application/json",
115
+ "X-CopilotKit-Webhook-Stage": WebhookStage.BeforeRequest,
116
+ },
117
+ body: JSON.stringify(payload),
118
+ signal: ac.signal,
119
+ });
120
+ } catch {
121
+ clearTimeout(to);
122
+ throw new Response(undefined, { status: 502 });
123
+ }
124
+ clearTimeout(to);
125
+
126
+ if (res.status >= 500) {
127
+ throw new Response(undefined, { status: 502 });
128
+ }
129
+ if (res.status >= 400) {
130
+ const errBody = await res.text();
131
+ throw new Response(errBody || null, {
132
+ status: res.status,
133
+ headers: {
134
+ "content-type": res.headers.get("content-type") || "application/json",
135
+ },
136
+ });
137
+ }
138
+ if (res.status === 204) return;
139
+
140
+ let json: unknown;
141
+ try {
142
+ json = await res.json();
143
+ } catch {
144
+ return;
145
+ }
146
+
147
+ if (json && typeof json === "object") {
148
+ const { headers, body } = json as {
149
+ headers?: Record<string, string>;
150
+ body?: unknown;
151
+ };
152
+ const init: RequestInit = {
153
+ method: request.method,
154
+ };
155
+ if (headers) {
156
+ init.headers = headers;
157
+ }
158
+ // Only add body for non-GET/HEAD requests
159
+ if (
160
+ body !== undefined &&
161
+ request.method !== "GET" &&
162
+ request.method !== "HEAD"
163
+ ) {
164
+ init.body = JSON.stringify(body);
165
+ }
166
+ return new Request(request.url, init);
167
+ }
168
+ return;
169
+ }
170
+
171
+ logger.warn({ mw }, "Unsupported beforeRequestMiddleware value – skipped");
172
+ return;
173
+ }
174
+
175
+ export async function callAfterRequestMiddleware({
176
+ runtime,
177
+ response,
178
+ path,
179
+ }: AfterRequestMiddlewareParameters): Promise<void> {
180
+ const mw = runtime.afterRequestMiddleware;
181
+ if (!mw) return;
182
+
183
+ if (typeof mw === "function") {
184
+ return (mw as AfterRequestMiddlewareFn)({ runtime, response, path });
185
+ }
186
+
187
+ if (isMiddlewareURL(mw)) {
188
+ const clone = response.clone();
189
+ const headersObj: Record<string, string> = {};
190
+ clone.headers.forEach((v, k) => {
191
+ headersObj[k] = v;
192
+ });
193
+ let body = "";
194
+ try {
195
+ body = await clone.text();
196
+ } catch {
197
+ /* ignore */
198
+ }
199
+
200
+ const payload = {
201
+ status: clone.status,
202
+ headers: headersObj,
203
+ body,
204
+ };
205
+
206
+ const ac = new AbortController();
207
+ const to = setTimeout(() => ac.abort(), 2000);
208
+ let res: Response;
209
+ try {
210
+ res = await fetch(mw, {
211
+ method: "POST",
212
+ headers: {
213
+ "content-type": "application/json",
214
+ "X-CopilotKit-Webhook-Stage": WebhookStage.AfterRequest,
215
+ },
216
+ body: JSON.stringify(payload),
217
+ signal: ac.signal,
218
+ });
219
+ } finally {
220
+ clearTimeout(to);
221
+ }
222
+
223
+ if (!res.ok) {
224
+ throw new Error(
225
+ `after_request webhook ${mw} responded with ${res.status}`
226
+ );
227
+ }
228
+ return;
229
+ }
230
+
231
+ logger.warn({ mw }, "Unsupported afterRequestMiddleware value – skipped");
232
+ }