@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,150 @@
1
+ import { Hono } from "hono";
2
+ import { CopilotRuntime } from "./runtime";
3
+ import { handleRunAgent } from "./handlers/handle-run";
4
+ import { handleGetRuntimeInfo } from "./handlers/get-runtime-info";
5
+ import { handleTranscribe } from "./handlers/handle-transcribe";
6
+ import { logger } from "@copilotkitnext/shared";
7
+ import {
8
+ callBeforeRequestMiddleware,
9
+ callAfterRequestMiddleware,
10
+ } from "./middleware";
11
+ import { handleConnectAgent } from "./handlers/handle-connect";
12
+
13
+ interface CopilotEndpointParams {
14
+ runtime: CopilotRuntime;
15
+ basePath: string;
16
+ }
17
+
18
+ // Define the context variables type
19
+ type CopilotEndpointContext = {
20
+ Variables: {
21
+ modifiedRequest?: Request;
22
+ };
23
+ };
24
+
25
+ export function createCopilotEndpoint({
26
+ runtime,
27
+ basePath,
28
+ }: CopilotEndpointParams) {
29
+ const app = new Hono<CopilotEndpointContext>();
30
+
31
+ return app
32
+ .basePath(basePath)
33
+ .use("*", async (c, next) => {
34
+ const request = c.req.raw;
35
+ const path = c.req.path;
36
+
37
+ try {
38
+ const maybeModifiedRequest = await callBeforeRequestMiddleware({
39
+ runtime,
40
+ request,
41
+ path,
42
+ });
43
+ if (maybeModifiedRequest) {
44
+ c.set("modifiedRequest", maybeModifiedRequest);
45
+ }
46
+ } catch (error) {
47
+ logger.error(
48
+ { err: error, url: request.url, path },
49
+ "Error running before request middleware"
50
+ );
51
+ if (error instanceof Response) {
52
+ return error;
53
+ }
54
+ throw error;
55
+ }
56
+
57
+ await next();
58
+ })
59
+ .use("*", async (c, next) => {
60
+ await next();
61
+
62
+ const response = c.res;
63
+ const path = c.req.path;
64
+
65
+ // Non-blocking after middleware
66
+ callAfterRequestMiddleware({
67
+ runtime,
68
+ response,
69
+ path,
70
+ }).catch((error) => {
71
+ logger.error(
72
+ { err: error, url: c.req.url, path },
73
+ "Error running after request middleware"
74
+ );
75
+ });
76
+ })
77
+ .post("/agent/:agentId/run", async (c) => {
78
+ const agentId = c.req.param("agentId");
79
+ const request = c.get("modifiedRequest") || c.req.raw;
80
+
81
+ try {
82
+ return await handleRunAgent({
83
+ runtime,
84
+ request,
85
+ agentId,
86
+ });
87
+ } catch (error) {
88
+ logger.error(
89
+ { err: error, url: request.url, path: c.req.path },
90
+ "Error running request handler"
91
+ );
92
+ throw error;
93
+ }
94
+ })
95
+ .post("/agent/:agentId/connect", async (c) => {
96
+ const agentId = c.req.param("agentId");
97
+ const request = c.get("modifiedRequest") || c.req.raw;
98
+
99
+ try {
100
+ return await handleConnectAgent({
101
+ runtime,
102
+ request,
103
+ agentId,
104
+ });
105
+ } catch (error) {
106
+ logger.error(
107
+ { err: error, url: request.url, path: c.req.path },
108
+ "Error running request handler"
109
+ );
110
+ throw error;
111
+ }
112
+ })
113
+ .get("/info", async (c) => {
114
+ const request = c.get("modifiedRequest") || c.req.raw;
115
+
116
+ try {
117
+ return await handleGetRuntimeInfo({
118
+ runtime,
119
+ request,
120
+ });
121
+ } catch (error) {
122
+ logger.error(
123
+ { err: error, url: request.url, path: c.req.path },
124
+ "Error running request handler"
125
+ );
126
+ throw error;
127
+ }
128
+ })
129
+ .post("/transcribe", async (c) => {
130
+ const request = c.get("modifiedRequest") || c.req.raw;
131
+
132
+ try {
133
+ return await handleTranscribe({
134
+ runtime,
135
+ request,
136
+ });
137
+ } catch (error) {
138
+ logger.error(
139
+ { err: error, url: request.url, path: c.req.path },
140
+ "Error running request handler"
141
+ );
142
+ throw error;
143
+ }
144
+ })
145
+ .notFound((c) => {
146
+ return c.json({ error: "Not found" }, 404);
147
+ });
148
+
149
+ // return app;
150
+ }
package/src/handler.ts ADDED
@@ -0,0 +1,3 @@
1
+ export type CopilotKitRequestHandler = (params: {
2
+ request: Request;
3
+ }) => Promise<Response>;
@@ -0,0 +1,50 @@
1
+ import { CopilotRuntime } from "../runtime";
2
+ import { AgentDescription, RuntimeInfo } from "@copilotkitnext/shared";
3
+ import { VERSION } from "../runtime";
4
+
5
+ interface HandleGetRuntimeInfoParameters {
6
+ runtime: CopilotRuntime;
7
+ request: Request;
8
+ }
9
+
10
+ export async function handleGetRuntimeInfo({
11
+ runtime,
12
+ }: HandleGetRuntimeInfoParameters) {
13
+ try {
14
+ const agents = await runtime.agents;
15
+
16
+ const agentsDict = Object.entries(agents).reduce(
17
+ (acc, [name, agent]) => {
18
+ acc[name] = {
19
+ name,
20
+ description: agent.description,
21
+ className: agent.constructor.name,
22
+ };
23
+ return acc;
24
+ },
25
+ {} as Record<string, AgentDescription>
26
+ );
27
+
28
+ const runtimeInfo: RuntimeInfo = {
29
+ version: VERSION,
30
+ agents: agentsDict,
31
+ audioFileTranscriptionEnabled: !!runtime.transcriptionService,
32
+ };
33
+
34
+ return new Response(JSON.stringify(runtimeInfo), {
35
+ status: 200,
36
+ headers: { "Content-Type": "application/json" },
37
+ });
38
+ } catch (error) {
39
+ return new Response(
40
+ JSON.stringify({
41
+ error: "Failed to retrieve runtime information",
42
+ message: error instanceof Error ? error.message : "Unknown error",
43
+ }),
44
+ {
45
+ status: 500,
46
+ headers: { "Content-Type": "application/json" },
47
+ }
48
+ );
49
+ }
50
+ }
@@ -0,0 +1,144 @@
1
+ import { RunAgentInput, RunAgentInputSchema } from "@ag-ui/client";
2
+ import { EventEncoder } from "@ag-ui/encoder";
3
+ import { CopilotRuntime } from "../runtime";
4
+
5
+ interface ConnectAgentParameters {
6
+ request: Request;
7
+ runtime: CopilotRuntime;
8
+ agentId: string;
9
+ }
10
+
11
+ export async function handleConnectAgent({
12
+ runtime,
13
+ request,
14
+ agentId,
15
+ }: ConnectAgentParameters) {
16
+ try {
17
+ const agents = await runtime.agents;
18
+
19
+ // Check if the requested agent exists
20
+ if (!agents[agentId]) {
21
+ return new Response(
22
+ JSON.stringify({
23
+ error: "Agent not found",
24
+ message: `Agent '${agentId}' does not exist`,
25
+ }),
26
+ {
27
+ status: 404,
28
+ headers: { "Content-Type": "application/json" },
29
+ }
30
+ );
31
+ }
32
+
33
+ const stream = new TransformStream();
34
+ const writer = stream.writable.getWriter();
35
+ const encoder = new EventEncoder();
36
+ let streamClosed = false;
37
+
38
+ // Process the request in the background
39
+ (async () => {
40
+ let input: RunAgentInput;
41
+ try {
42
+ const requestBody = await request.json();
43
+ input = RunAgentInputSchema.parse(requestBody);
44
+ } catch {
45
+ return new Response(
46
+ JSON.stringify({
47
+ error: "Invalid request body",
48
+ }),
49
+ { status: 400 }
50
+ );
51
+ }
52
+
53
+ runtime.runner
54
+ .connect({
55
+ threadId: input.threadId,
56
+ })
57
+ .subscribe({
58
+ next: async (event) => {
59
+ if (!request.signal.aborted && !streamClosed) {
60
+ try {
61
+ await writer.write(encoder.encode(event));
62
+ } catch (error) {
63
+ if (error instanceof Error && error.name === "AbortError") {
64
+ streamClosed = true;
65
+ }
66
+ }
67
+ }
68
+ },
69
+ error: async (error) => {
70
+ console.error("Error running agent:", error);
71
+ if (!streamClosed) {
72
+ try {
73
+ await writer.close();
74
+ streamClosed = true;
75
+ } catch {
76
+ // Stream already closed
77
+ }
78
+ }
79
+ },
80
+ complete: async () => {
81
+ if (!streamClosed) {
82
+ try {
83
+ await writer.close();
84
+ streamClosed = true;
85
+ } catch {
86
+ // Stream already closed
87
+ }
88
+ }
89
+ },
90
+ });
91
+ })().catch((error) => {
92
+ console.error("Error running agent:", error);
93
+ console.error(
94
+ "Error stack:",
95
+ error instanceof Error ? error.stack : "No stack trace"
96
+ );
97
+ console.error("Error details:", {
98
+ name: error instanceof Error ? error.name : "Unknown",
99
+ message: error instanceof Error ? error.message : String(error),
100
+ cause: error instanceof Error ? error.cause : undefined,
101
+ });
102
+ if (!streamClosed) {
103
+ try {
104
+ writer.close();
105
+ streamClosed = true;
106
+ } catch {
107
+ // Stream already closed
108
+ }
109
+ }
110
+ });
111
+
112
+ // Return the SSE response
113
+ return new Response(stream.readable, {
114
+ status: 200,
115
+ headers: {
116
+ "Content-Type": "text/event-stream",
117
+ "Cache-Control": "no-cache",
118
+ Connection: "keep-alive",
119
+ },
120
+ });
121
+ } catch (error) {
122
+ console.error("Error running agent:", error);
123
+ console.error(
124
+ "Error stack:",
125
+ error instanceof Error ? error.stack : "No stack trace"
126
+ );
127
+ console.error("Error details:", {
128
+ name: error instanceof Error ? error.name : "Unknown",
129
+ message: error instanceof Error ? error.message : String(error),
130
+ cause: error instanceof Error ? error.cause : undefined,
131
+ });
132
+
133
+ return new Response(
134
+ JSON.stringify({
135
+ error: "Failed to run agent",
136
+ message: error instanceof Error ? error.message : "Unknown error",
137
+ }),
138
+ {
139
+ status: 500,
140
+ headers: { "Content-Type": "application/json" },
141
+ }
142
+ );
143
+ }
144
+ }
@@ -0,0 +1,156 @@
1
+ import {
2
+ AbstractAgent,
3
+ RunAgentInput,
4
+ RunAgentInputSchema,
5
+ } from "@ag-ui/client";
6
+ import { EventEncoder } from "@ag-ui/encoder";
7
+ import { CopilotRuntime } from "../runtime";
8
+
9
+ interface RunAgentParameters {
10
+ request: Request;
11
+ runtime: CopilotRuntime;
12
+ agentId: string;
13
+ }
14
+
15
+ export async function handleRunAgent({
16
+ runtime,
17
+ request,
18
+ agentId,
19
+ }: RunAgentParameters) {
20
+ try {
21
+ const agents = await runtime.agents;
22
+
23
+ // Check if the requested agent exists
24
+ if (!agents[agentId]) {
25
+ return new Response(
26
+ JSON.stringify({
27
+ error: "Agent not found",
28
+ message: `Agent '${agentId}' does not exist`,
29
+ }),
30
+ {
31
+ status: 404,
32
+ headers: { "Content-Type": "application/json" },
33
+ }
34
+ );
35
+ }
36
+
37
+ const agent = agents[agentId].clone() as AbstractAgent;
38
+
39
+ const stream = new TransformStream();
40
+ const writer = stream.writable.getWriter();
41
+ const encoder = new EventEncoder();
42
+ let streamClosed = false;
43
+
44
+ // Process the request in the background
45
+ (async () => {
46
+ let input: RunAgentInput;
47
+ try {
48
+ const requestBody = await request.json();
49
+ input = RunAgentInputSchema.parse(requestBody);
50
+ } catch {
51
+ return new Response(
52
+ JSON.stringify({
53
+ error: "Invalid request body",
54
+ }),
55
+ { status: 400 }
56
+ );
57
+ }
58
+
59
+ agent.setMessages(input.messages);
60
+ agent.setState(input.state);
61
+ agent.threadId = input.threadId;
62
+
63
+ runtime.runner
64
+ .run({
65
+ threadId: input.threadId,
66
+ agent,
67
+ input,
68
+ })
69
+ .subscribe({
70
+ next: async (event) => {
71
+ if (!request.signal.aborted && !streamClosed) {
72
+ try {
73
+ await writer.write(encoder.encode(event));
74
+ } catch (error) {
75
+ if (error instanceof Error && error.name === 'AbortError') {
76
+ streamClosed = true;
77
+ }
78
+ }
79
+ }
80
+ },
81
+ error: async (error) => {
82
+ console.error("Error running agent:", error);
83
+ if (!streamClosed) {
84
+ try {
85
+ await writer.close();
86
+ streamClosed = true;
87
+ } catch {
88
+ // Stream already closed
89
+ }
90
+ }
91
+ },
92
+ complete: async () => {
93
+ if (!streamClosed) {
94
+ try {
95
+ await writer.close();
96
+ streamClosed = true;
97
+ } catch {
98
+ // Stream already closed
99
+ }
100
+ }
101
+ },
102
+ });
103
+ })().catch((error) => {
104
+ console.error("Error running agent:", error);
105
+ console.error(
106
+ "Error stack:",
107
+ error instanceof Error ? error.stack : "No stack trace"
108
+ );
109
+ console.error("Error details:", {
110
+ name: error instanceof Error ? error.name : "Unknown",
111
+ message: error instanceof Error ? error.message : String(error),
112
+ cause: error instanceof Error ? error.cause : undefined,
113
+ });
114
+ if (!streamClosed) {
115
+ try {
116
+ writer.close();
117
+ streamClosed = true;
118
+ } catch {
119
+ // Stream already closed
120
+ }
121
+ }
122
+ });
123
+
124
+ // Return the SSE response
125
+ return new Response(stream.readable, {
126
+ status: 200,
127
+ headers: {
128
+ "Content-Type": "text/event-stream",
129
+ "Cache-Control": "no-cache",
130
+ Connection: "keep-alive",
131
+ },
132
+ });
133
+ } catch (error) {
134
+ console.error("Error running agent:", error);
135
+ console.error(
136
+ "Error stack:",
137
+ error instanceof Error ? error.stack : "No stack trace"
138
+ );
139
+ console.error("Error details:", {
140
+ name: error instanceof Error ? error.name : "Unknown",
141
+ message: error instanceof Error ? error.message : String(error),
142
+ cause: error instanceof Error ? error.cause : undefined,
143
+ });
144
+
145
+ return new Response(
146
+ JSON.stringify({
147
+ error: "Failed to run agent",
148
+ message: error instanceof Error ? error.message : "Unknown error",
149
+ }),
150
+ {
151
+ status: 500,
152
+ headers: { "Content-Type": "application/json" },
153
+ }
154
+ );
155
+ }
156
+ }
@@ -0,0 +1,126 @@
1
+ import { CopilotRuntime } from "../runtime";
2
+
3
+ interface HandleTranscribeParameters {
4
+ runtime: CopilotRuntime;
5
+ request: Request;
6
+ }
7
+
8
+ export async function handleTranscribe({
9
+ runtime,
10
+ request,
11
+ }: HandleTranscribeParameters) {
12
+ try {
13
+ // Check if transcription service is configured
14
+ if (!runtime.transcriptionService) {
15
+ return new Response(
16
+ JSON.stringify({
17
+ error: "Transcription service not configured",
18
+ message:
19
+ "No transcription service has been configured in the runtime",
20
+ }),
21
+ {
22
+ status: 503,
23
+ headers: { "Content-Type": "application/json" },
24
+ }
25
+ );
26
+ }
27
+
28
+ // Check if request has form data
29
+ const contentType = request.headers.get("content-type");
30
+ if (!contentType || !contentType.includes("multipart/form-data")) {
31
+ return new Response(
32
+ JSON.stringify({
33
+ error: "Invalid content type",
34
+ message:
35
+ "Request must contain multipart/form-data with an audio file",
36
+ }),
37
+ {
38
+ status: 400,
39
+ headers: { "Content-Type": "application/json" },
40
+ }
41
+ );
42
+ }
43
+
44
+ // Extract form data
45
+ const formData = await request.formData();
46
+ const audioFile = formData.get("audio") as File | null;
47
+
48
+ if (!audioFile || !(audioFile instanceof File)) {
49
+ return new Response(
50
+ JSON.stringify({
51
+ error: "Missing audio file",
52
+ message:
53
+ "No audio file found in form data. Please include an 'audio' field.",
54
+ }),
55
+ {
56
+ status: 400,
57
+ headers: { "Content-Type": "application/json" },
58
+ }
59
+ );
60
+ }
61
+
62
+ // Validate file type (basic check)
63
+ const validAudioTypes = [
64
+ "audio/mpeg",
65
+ "audio/mp3",
66
+ "audio/mp4",
67
+ "audio/wav",
68
+ "audio/webm",
69
+ "audio/ogg",
70
+ "audio/flac",
71
+ "audio/aac",
72
+ ];
73
+
74
+ // Allow empty types and application/octet-stream (common fallback for unknown types)
75
+ const isValidType =
76
+ validAudioTypes.includes(audioFile.type) ||
77
+ audioFile.type === "" ||
78
+ audioFile.type === "application/octet-stream";
79
+
80
+ if (!isValidType) {
81
+ return new Response(
82
+ JSON.stringify({
83
+ error: "Invalid file type",
84
+ message: `Unsupported audio file type: ${audioFile.type}. Supported types: ${validAudioTypes.join(", ")}, or files with unknown/empty types`,
85
+ }),
86
+ {
87
+ status: 400,
88
+ headers: { "Content-Type": "application/json" },
89
+ }
90
+ );
91
+ }
92
+
93
+ // Transcribe the audio file with options
94
+ const transcription = await runtime.transcriptionService.transcribeFile({
95
+ audioFile,
96
+ mimeType: audioFile.type,
97
+ size: audioFile.size,
98
+ });
99
+
100
+ return new Response(
101
+ JSON.stringify({
102
+ text: transcription,
103
+ size: audioFile.size,
104
+ type: audioFile.type,
105
+ }),
106
+ {
107
+ status: 200,
108
+ headers: { "Content-Type": "application/json" },
109
+ }
110
+ );
111
+ } catch (error) {
112
+ return new Response(
113
+ JSON.stringify({
114
+ error: "Transcription failed",
115
+ message:
116
+ error instanceof Error
117
+ ? error.message
118
+ : "Unknown error occurred during transcription",
119
+ }),
120
+ {
121
+ status: 500,
122
+ headers: { "Content-Type": "application/json" },
123
+ }
124
+ );
125
+ }
126
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./runtime";
2
+ export * from "./endpoint";
3
+
4
+ // Export agent runners
5
+ export { InMemoryAgentRunner } from "./runner/in-memory";
6
+ export { SqliteAgentRunner } from "./runner/sqlite";
7
+ export { EnterpriseAgentRunner } from "./runner/enterprise";
8
+ export type { EnterpriseAgentRunnerOptions } from "./runner/enterprise";