@copilotkit/runtime 1.55.3 → 1.56.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.
- package/dist/agent/converters/tanstack.cjs.map +1 -1
- package/dist/agent/converters/tanstack.d.cts +6 -19
- package/dist/agent/converters/tanstack.d.cts.map +1 -1
- package/dist/agent/converters/tanstack.d.mts +6 -19
- package/dist/agent/converters/tanstack.d.mts.map +1 -1
- package/dist/agent/converters/tanstack.mjs.map +1 -1
- package/dist/agent/index.cjs +16 -2
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +12 -1
- package/dist/agent/index.d.cts.map +1 -1
- package/dist/agent/index.d.mts +12 -1
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +16 -2
- package/dist/agent/index.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +1 -1
- package/dist/lib/index.cjs +1 -1
- package/dist/lib/index.d.cts +2 -1
- package/dist/lib/index.d.cts.map +1 -1
- package/dist/lib/index.d.mts +2 -1
- package/dist/lib/index.d.mts.map +1 -1
- package/dist/lib/index.mjs +1 -1
- package/dist/lib/integrations/node-http/index.cjs +4 -1
- package/dist/lib/integrations/node-http/index.cjs.map +1 -1
- package/dist/lib/integrations/node-http/index.d.cts.map +1 -1
- package/dist/lib/integrations/node-http/index.d.mts.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +4 -1
- package/dist/lib/integrations/node-http/index.mjs.map +1 -1
- package/dist/lib/integrations/shared.cjs +1 -1
- package/dist/lib/integrations/shared.d.cts +1 -1
- package/dist/lib/integrations/shared.d.mts +1 -1
- package/dist/lib/integrations/shared.mjs +1 -1
- package/dist/lib/runtime/copilot-runtime.cjs +25 -5
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts +15 -3
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts +15 -3
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +25 -5
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.cjs +21 -4
- package/dist/lib/runtime/mcp-tools-utils.cjs.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.d.cts.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.d.mts.map +1 -1
- package/dist/lib/runtime/mcp-tools-utils.mjs +21 -4
- package/dist/lib/runtime/mcp-tools-utils.mjs.map +1 -1
- package/dist/package.cjs +6 -5
- package/dist/package.mjs +6 -5
- package/dist/service-adapters/anthropic/anthropic-adapter.cjs +11 -3
- package/dist/service-adapters/anthropic/anthropic-adapter.cjs.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.d.cts +6 -0
- package/dist/service-adapters/anthropic/anthropic-adapter.d.cts.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.d.mts +6 -0
- package/dist/service-adapters/anthropic/anthropic-adapter.d.mts.map +1 -1
- package/dist/service-adapters/anthropic/anthropic-adapter.mjs +11 -3
- package/dist/service-adapters/anthropic/anthropic-adapter.mjs.map +1 -1
- package/dist/service-adapters/anthropic/utils.cjs +27 -1
- package/dist/service-adapters/anthropic/utils.cjs.map +1 -1
- package/dist/service-adapters/anthropic/utils.mjs +27 -1
- package/dist/service-adapters/anthropic/utils.mjs.map +1 -1
- package/dist/service-adapters/langchain/utils.cjs +1 -1
- package/dist/service-adapters/langchain/utils.cjs.map +1 -1
- package/dist/service-adapters/langchain/utils.mjs +1 -1
- package/dist/service-adapters/langchain/utils.mjs.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.cjs +3 -2
- package/dist/service-adapters/openai/openai-adapter.cjs.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.d.cts +6 -0
- package/dist/service-adapters/openai/openai-adapter.d.cts.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.d.mts +6 -0
- package/dist/service-adapters/openai/openai-adapter.d.mts.map +1 -1
- package/dist/service-adapters/openai/openai-adapter.mjs +4 -3
- package/dist/service-adapters/openai/openai-adapter.mjs.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.cjs +8 -9
- package/dist/service-adapters/openai/openai-assistant-adapter.cjs.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.d.cts.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.d.mts.map +1 -1
- package/dist/service-adapters/openai/openai-assistant-adapter.mjs +9 -10
- package/dist/service-adapters/openai/openai-assistant-adapter.mjs.map +1 -1
- package/dist/service-adapters/openai/utils.cjs +53 -0
- package/dist/service-adapters/openai/utils.cjs.map +1 -1
- package/dist/service-adapters/openai/utils.mjs +51 -1
- package/dist/service-adapters/openai/utils.mjs.map +1 -1
- package/dist/v2/index.cjs +1 -0
- package/dist/v2/index.d.cts +3 -3
- package/dist/v2/index.d.mts +3 -3
- package/dist/v2/index.mjs +2 -2
- package/dist/v2/runtime/core/middleware-sse-parser.cjs +5 -2
- package/dist/v2/runtime/core/middleware-sse-parser.cjs.map +1 -1
- package/dist/v2/runtime/core/middleware-sse-parser.mjs +5 -2
- package/dist/v2/runtime/core/middleware-sse-parser.mjs.map +1 -1
- package/dist/v2/runtime/core/runtime.cjs +25 -0
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts +53 -4
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts +53 -4
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs +26 -2
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.cjs +18 -10
- package/dist/v2/runtime/handlers/get-runtime-info.cjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.mjs +19 -11
- package/dist/v2/runtime/handlers/get-runtime-info.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs +1 -1
- package/dist/v2/runtime/handlers/handle-connect.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs +1 -1
- package/dist/v2/runtime/handlers/handle-connect.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.cjs +8 -2
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +8 -2
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-stop.cjs +2 -1
- package/dist/v2/runtime/handlers/handle-stop.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-stop.mjs +2 -1
- package/dist/v2/runtime/handlers/handle-stop.mjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.cjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.mjs +1 -1
- package/dist/v2/runtime/handlers/intelligence/thread-names.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs +3 -2
- package/dist/v2/runtime/handlers/shared/agent-utils.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs +3 -2
- package/dist/v2/runtime/handlers/shared/agent-utils.mjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs +40 -1
- package/dist/v2/runtime/handlers/shared/sse-response.cjs.map +1 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs +40 -1
- package/dist/v2/runtime/handlers/shared/sse-response.mjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.cjs +3 -1
- package/dist/v2/runtime/handlers/sse/run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/sse/run.mjs +3 -1
- package/dist/v2/runtime/handlers/sse/run.mjs.map +1 -1
- package/dist/v2/runtime/index.d.cts +1 -1
- package/dist/v2/runtime/index.d.mts +1 -1
- package/package.json +7 -6
- package/src/agent/__tests__/capabilities.test.ts +81 -0
- package/src/agent/__tests__/provider-id-collision.test.ts +195 -0
- package/src/agent/converters/tanstack.ts +15 -7
- package/src/agent/index.ts +52 -11
- package/src/lib/integrations/node-http/__tests__/request-duck-type.test.ts +66 -0
- package/src/lib/integrations/node-http/index.ts +15 -1
- package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +30 -1
- package/src/lib/runtime/__tests__/on-after-request.test.ts +122 -0
- package/src/lib/runtime/__tests__/v1-agent-factory.test.ts +109 -0
- package/src/lib/runtime/copilot-runtime.ts +54 -5
- package/src/lib/runtime/mcp-tools-utils.ts +41 -6
- package/src/service-adapters/anthropic/anthropic-adapter.ts +22 -2
- package/src/service-adapters/anthropic/utils.ts +60 -1
- package/src/service-adapters/langchain/utils.ts +1 -1
- package/src/service-adapters/openai/__tests__/openai-v5-compat.test.ts +177 -0
- package/src/service-adapters/openai/openai-adapter.ts +17 -2
- package/src/service-adapters/openai/openai-assistant-adapter.ts +7 -9
- package/src/service-adapters/openai/utils.ts +100 -0
- package/src/v2/runtime/__tests__/agents-factory.test.ts +136 -0
- package/src/v2/runtime/__tests__/debug-sse-response.test.ts +302 -0
- package/src/v2/runtime/__tests__/get-runtime-info.test.ts +134 -1
- package/src/v2/runtime/__tests__/middleware-sse-parser.test.ts +50 -0
- package/src/v2/runtime/core/middleware-sse-parser.ts +12 -2
- package/src/v2/runtime/core/runtime.ts +90 -2
- package/src/v2/runtime/handlers/get-runtime-info.ts +33 -8
- package/src/v2/runtime/handlers/handle-connect.ts +1 -1
- package/src/v2/runtime/handlers/handle-run.ts +16 -2
- package/src/v2/runtime/handlers/handle-stop.ts +2 -1
- package/src/v2/runtime/handlers/intelligence/thread-names.ts +1 -1
- package/src/v2/runtime/handlers/shared/agent-utils.ts +3 -2
- package/src/v2/runtime/handlers/shared/sse-response.ts +69 -0
- package/src/v2/runtime/handlers/sse/run.ts +9 -0
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +268 -0
- package/tests/service-adapters/anthropic/utils-token-trimming.test.ts +301 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
isOpenAIV5,
|
|
4
|
+
getChatCompletionsForStreaming,
|
|
5
|
+
retrieveThreadRun,
|
|
6
|
+
submitToolOutputsStream,
|
|
7
|
+
} from "../utils";
|
|
8
|
+
import type OpenAI from "openai";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests for OpenAI SDK v4/v5 compatibility layer.
|
|
12
|
+
*
|
|
13
|
+
* In v5, the `beta.chat` namespace was removed and promoted to `chat`.
|
|
14
|
+
* In v5, methods with multiple path params switched to named params
|
|
15
|
+
* (e.g. runs.retrieve(runId, { thread_id }) instead of runs.retrieve(threadId, runId)).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function createMockV4Client() {
|
|
19
|
+
const streamFn = vi.fn();
|
|
20
|
+
return {
|
|
21
|
+
client: {
|
|
22
|
+
beta: {
|
|
23
|
+
chat: {
|
|
24
|
+
completions: { stream: streamFn },
|
|
25
|
+
},
|
|
26
|
+
threads: {
|
|
27
|
+
create: vi.fn(),
|
|
28
|
+
runs: {
|
|
29
|
+
retrieve: vi.fn().mockResolvedValue({ id: "run_xyz" }),
|
|
30
|
+
stream: vi.fn(),
|
|
31
|
+
submitToolOutputsStream: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
messages: {
|
|
34
|
+
create: vi.fn(),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
chat: {
|
|
39
|
+
completions: {
|
|
40
|
+
stream: vi.fn(), // exists but should NOT be used for streaming in v4
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
} as unknown as OpenAI,
|
|
44
|
+
streamFn,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createMockV5Client() {
|
|
49
|
+
const streamFn = vi.fn();
|
|
50
|
+
return {
|
|
51
|
+
client: {
|
|
52
|
+
// v5: beta.chat is gone, only beta.threads remains
|
|
53
|
+
beta: {
|
|
54
|
+
threads: {
|
|
55
|
+
create: vi.fn(),
|
|
56
|
+
runs: {
|
|
57
|
+
retrieve: vi.fn().mockResolvedValue({ id: "run_xyz" }),
|
|
58
|
+
stream: vi.fn(),
|
|
59
|
+
submitToolOutputsStream: vi.fn(),
|
|
60
|
+
},
|
|
61
|
+
messages: {
|
|
62
|
+
create: vi.fn(),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
chat: {
|
|
67
|
+
completions: {
|
|
68
|
+
stream: streamFn,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
} as unknown as OpenAI,
|
|
72
|
+
streamFn,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("isOpenAIV5", () => {
|
|
77
|
+
it("returns false for a v4 client (beta.chat exists)", () => {
|
|
78
|
+
const { client } = createMockV4Client();
|
|
79
|
+
expect(isOpenAIV5(client)).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns true for a v5 client (beta.chat does not exist)", () => {
|
|
83
|
+
const { client } = createMockV5Client();
|
|
84
|
+
expect(isOpenAIV5(client)).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("returns true when beta is entirely missing", () => {
|
|
88
|
+
const client = { chat: { completions: {} } } as unknown as OpenAI;
|
|
89
|
+
expect(isOpenAIV5(client)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("getChatCompletionsForStreaming", () => {
|
|
94
|
+
it("returns beta.chat.completions for a v4 client", () => {
|
|
95
|
+
const { client } = createMockV4Client();
|
|
96
|
+
const completions = getChatCompletionsForStreaming(client);
|
|
97
|
+
// Should be the beta version, not the top-level one
|
|
98
|
+
expect(completions).not.toBe(client.chat.completions);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns chat.completions for a v5 client", () => {
|
|
102
|
+
const { client } = createMockV5Client();
|
|
103
|
+
const completions = getChatCompletionsForStreaming(client);
|
|
104
|
+
expect(completions).toBe(client.chat.completions);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("stream() on v4 calls beta.chat.completions.stream", () => {
|
|
108
|
+
const { client, streamFn } = createMockV4Client();
|
|
109
|
+
const completions = getChatCompletionsForStreaming(client);
|
|
110
|
+
completions.stream({ model: "gpt-4o", messages: [] });
|
|
111
|
+
expect(streamFn).toHaveBeenCalledWith({ model: "gpt-4o", messages: [] });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("stream() on v5 calls chat.completions.stream", () => {
|
|
115
|
+
const { client, streamFn } = createMockV5Client();
|
|
116
|
+
const completions = getChatCompletionsForStreaming(client);
|
|
117
|
+
completions.stream({ model: "gpt-4o", messages: [] });
|
|
118
|
+
expect(streamFn).toHaveBeenCalledWith({ model: "gpt-4o", messages: [] });
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("retrieveThreadRun", () => {
|
|
123
|
+
it("v4: calls retrieve with positional args (threadId, runId)", async () => {
|
|
124
|
+
const { client } = createMockV4Client();
|
|
125
|
+
const retrieveFn = client.beta.threads.runs.retrieve as ReturnType<
|
|
126
|
+
typeof vi.fn
|
|
127
|
+
>;
|
|
128
|
+
|
|
129
|
+
await retrieveThreadRun(client, "thread_abc", "run_xyz");
|
|
130
|
+
|
|
131
|
+
expect(retrieveFn).toHaveBeenCalledWith("thread_abc", "run_xyz");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("v5: calls retrieve with named path params (runId, { thread_id })", async () => {
|
|
135
|
+
const { client } = createMockV5Client();
|
|
136
|
+
const retrieveFn = client.beta.threads.runs.retrieve as ReturnType<
|
|
137
|
+
typeof vi.fn
|
|
138
|
+
>;
|
|
139
|
+
|
|
140
|
+
await retrieveThreadRun(client, "thread_abc", "run_xyz");
|
|
141
|
+
|
|
142
|
+
expect(retrieveFn).toHaveBeenCalledWith("run_xyz", {
|
|
143
|
+
thread_id: "thread_abc",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("submitToolOutputsStream", () => {
|
|
149
|
+
it("v4: calls with positional args (threadId, runId, body)", () => {
|
|
150
|
+
const { client } = createMockV4Client();
|
|
151
|
+
const submitFn = client.beta.threads.runs
|
|
152
|
+
.submitToolOutputsStream as ReturnType<typeof vi.fn>;
|
|
153
|
+
const body = {
|
|
154
|
+
tool_outputs: [{ tool_call_id: "tc_1", output: "result" }],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
submitToolOutputsStream(client, "thread_abc", "run_xyz", body);
|
|
158
|
+
|
|
159
|
+
expect(submitFn).toHaveBeenCalledWith("thread_abc", "run_xyz", body);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("v5: calls with named path params (runId, { thread_id, ...body })", () => {
|
|
163
|
+
const { client } = createMockV5Client();
|
|
164
|
+
const submitFn = client.beta.threads.runs
|
|
165
|
+
.submitToolOutputsStream as ReturnType<typeof vi.fn>;
|
|
166
|
+
const body = {
|
|
167
|
+
tool_outputs: [{ tool_call_id: "tc_1", output: "result" }],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
submitToolOutputsStream(client, "thread_abc", "run_xyz", body);
|
|
171
|
+
|
|
172
|
+
expect(submitFn).toHaveBeenCalledWith("run_xyz", {
|
|
173
|
+
thread_id: "thread_abc",
|
|
174
|
+
...body,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
convertActionInputToOpenAITool,
|
|
62
62
|
convertMessageToOpenAIMessage,
|
|
63
63
|
limitMessagesToTokenCount,
|
|
64
|
+
getChatCompletionsForStreaming,
|
|
64
65
|
} from "./utils";
|
|
65
66
|
import { randomUUID } from "@copilotkit/shared";
|
|
66
67
|
import { convertServiceAdapterError, getSdkClientOptions } from "../shared";
|
|
@@ -96,6 +97,12 @@ export interface OpenAIAdapterParams {
|
|
|
96
97
|
* @default false
|
|
97
98
|
*/
|
|
98
99
|
keepSystemRole?: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Optional maximum input token limit. Overrides the default model-based limit
|
|
103
|
+
* used when trimming messages to fit the context window.
|
|
104
|
+
*/
|
|
105
|
+
maxInputTokens?: number;
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
export class OpenAIAdapter implements CopilotServiceAdapter {
|
|
@@ -105,6 +112,7 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
|
|
|
105
112
|
private disableParallelToolCalls: boolean = false;
|
|
106
113
|
private _openai: OpenAI;
|
|
107
114
|
private keepSystemRole: boolean = false;
|
|
115
|
+
private maxInputTokens?: number;
|
|
108
116
|
|
|
109
117
|
public get openai(): OpenAI {
|
|
110
118
|
return this._openai;
|
|
@@ -124,6 +132,7 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
|
|
|
124
132
|
}
|
|
125
133
|
this.disableParallelToolCalls = params?.disableParallelToolCalls || false;
|
|
126
134
|
this.keepSystemRole = params?.keepSystemRole ?? false;
|
|
135
|
+
this.maxInputTokens = params?.maxInputTokens;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
138
|
getLanguageModel(): LanguageModel {
|
|
@@ -191,7 +200,12 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
|
|
|
191
200
|
let openaiMessages = filteredMessages.map((m) =>
|
|
192
201
|
convertMessageToOpenAIMessage(m, { keepSystemRole: this.keepSystemRole }),
|
|
193
202
|
);
|
|
194
|
-
openaiMessages = limitMessagesToTokenCount(
|
|
203
|
+
openaiMessages = limitMessagesToTokenCount(
|
|
204
|
+
openaiMessages,
|
|
205
|
+
tools,
|
|
206
|
+
model,
|
|
207
|
+
this.maxInputTokens,
|
|
208
|
+
);
|
|
195
209
|
|
|
196
210
|
let toolChoice: any = forwardedParameters?.toolChoice;
|
|
197
211
|
if (forwardedParameters?.toolChoice === "function") {
|
|
@@ -203,7 +217,8 @@ export class OpenAIAdapter implements CopilotServiceAdapter {
|
|
|
203
217
|
|
|
204
218
|
try {
|
|
205
219
|
const openai = this.ensureOpenAI();
|
|
206
|
-
const
|
|
220
|
+
const completions = getChatCompletionsForStreaming(openai);
|
|
221
|
+
const stream = completions.stream({
|
|
207
222
|
model: model,
|
|
208
223
|
stream: true,
|
|
209
224
|
messages: openaiMessages,
|
|
@@ -43,6 +43,8 @@ import {
|
|
|
43
43
|
convertActionInputToOpenAITool,
|
|
44
44
|
convertMessageToOpenAIMessage,
|
|
45
45
|
convertSystemMessageToAssistantAPI,
|
|
46
|
+
retrieveThreadRun,
|
|
47
|
+
submitToolOutputsStream,
|
|
46
48
|
} from "./utils";
|
|
47
49
|
import { RuntimeEventSource } from "../events";
|
|
48
50
|
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
@@ -186,7 +188,7 @@ export class OpenAIAssistantAdapter implements CopilotServiceAdapter {
|
|
|
186
188
|
eventSource: RuntimeEventSource,
|
|
187
189
|
) {
|
|
188
190
|
const openai = this.ensureOpenAI();
|
|
189
|
-
let run = await openai
|
|
191
|
+
let run = await retrieveThreadRun(openai, threadId, runId);
|
|
190
192
|
|
|
191
193
|
if (!run.required_action) {
|
|
192
194
|
throw new Error("No tool outputs required");
|
|
@@ -219,14 +221,10 @@ export class OpenAIAssistantAdapter implements CopilotServiceAdapter {
|
|
|
219
221
|
};
|
|
220
222
|
});
|
|
221
223
|
|
|
222
|
-
const stream =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
tool_outputs: toolOutputs,
|
|
227
|
-
...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
|
|
228
|
-
},
|
|
229
|
-
);
|
|
224
|
+
const stream = submitToolOutputsStream(openai, threadId, runId, {
|
|
225
|
+
tool_outputs: toolOutputs,
|
|
226
|
+
...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
|
|
227
|
+
});
|
|
230
228
|
|
|
231
229
|
await this.streamResponse(stream, eventSource);
|
|
232
230
|
return runId;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type OpenAI from "openai";
|
|
1
2
|
import { Message } from "../../graphql/types/converted";
|
|
2
3
|
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
3
4
|
import {
|
|
@@ -10,6 +11,105 @@ import {
|
|
|
10
11
|
} from "openai/resources/chat";
|
|
11
12
|
import { parseJson } from "@copilotkit/shared";
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* OpenAI v4 exposes streaming completions under `beta.chat.completions`.
|
|
16
|
+
* v5 removed `beta.chat` and promoted streaming to `chat.completions`.
|
|
17
|
+
* These interfaces model the v4-specific shape so we can detect and access
|
|
18
|
+
* the beta namespace safely without `as any`.
|
|
19
|
+
*/
|
|
20
|
+
interface OpenAIV4BetaChat {
|
|
21
|
+
chat: {
|
|
22
|
+
completions: OpenAI["chat"]["completions"];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface OpenAIV4Beta extends OpenAI.Beta {
|
|
27
|
+
chat: OpenAIV4BetaChat["chat"];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type guard: checks whether the OpenAI client has the v4-era `beta.chat`
|
|
32
|
+
* namespace. Returns `false` for v5+ clients where `beta.chat` was removed.
|
|
33
|
+
*/
|
|
34
|
+
function hasV4BetaChat(beta: OpenAI["beta"] | undefined): beta is OpenAIV4Beta {
|
|
35
|
+
return beta != null && "chat" in beta && (beta as OpenAIV4Beta).chat != null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Detects whether the provided OpenAI client is v5+ by checking for the
|
|
40
|
+
* removal of the `beta.chat` namespace (which was promoted to `chat` in v5).
|
|
41
|
+
*/
|
|
42
|
+
export function isOpenAIV5(openai: OpenAI): boolean {
|
|
43
|
+
return !hasV4BetaChat(openai.beta);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns the chat completions object that supports `.stream()`.
|
|
48
|
+
* In v4 this lives under `openai.beta.chat.completions`;
|
|
49
|
+
* in v5 it was promoted to `openai.chat.completions`.
|
|
50
|
+
*/
|
|
51
|
+
export function getChatCompletionsForStreaming(
|
|
52
|
+
openai: OpenAI,
|
|
53
|
+
): OpenAI["chat"]["completions"] {
|
|
54
|
+
if (hasV4BetaChat(openai.beta)) {
|
|
55
|
+
return openai.beta.chat.completions;
|
|
56
|
+
}
|
|
57
|
+
return openai.chat.completions;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Retrieves a thread run, handling the v4→v5 API signature change.
|
|
62
|
+
* v4: retrieve(threadId, runId)
|
|
63
|
+
* v5: retrieve(runId, { thread_id: threadId })
|
|
64
|
+
*/
|
|
65
|
+
export async function retrieveThreadRun(
|
|
66
|
+
openai: OpenAI,
|
|
67
|
+
threadId: string,
|
|
68
|
+
runId: string,
|
|
69
|
+
): Promise<OpenAI.Beta.Threads.Runs.Run> {
|
|
70
|
+
if (isOpenAIV5(openai)) {
|
|
71
|
+
// v5 switched to named path params. The type definitions from whichever
|
|
72
|
+
// SDK version is installed won't match both signatures, so we call through
|
|
73
|
+
// a generic function reference. This is the one unavoidable boundary
|
|
74
|
+
// between two incompatible SDK type surfaces.
|
|
75
|
+
const retrieve = openai.beta.threads.runs.retrieve as {
|
|
76
|
+
(...args: unknown[]): Promise<OpenAI.Beta.Threads.Runs.Run>;
|
|
77
|
+
};
|
|
78
|
+
return retrieve(runId, { thread_id: threadId });
|
|
79
|
+
}
|
|
80
|
+
return openai.beta.threads.runs.retrieve(threadId, runId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Submits tool outputs as a stream, handling the v4→v5 API signature change.
|
|
85
|
+
* v4: submitToolOutputsStream(threadId, runId, body)
|
|
86
|
+
* v5: submitToolOutputsStream(runId, { thread_id, ...body })
|
|
87
|
+
*/
|
|
88
|
+
export function submitToolOutputsStream(
|
|
89
|
+
openai: OpenAI,
|
|
90
|
+
threadId: string,
|
|
91
|
+
runId: string,
|
|
92
|
+
body: {
|
|
93
|
+
tool_outputs: Array<{ tool_call_id: string; output: string }>;
|
|
94
|
+
parallel_tool_calls?: false;
|
|
95
|
+
},
|
|
96
|
+
) {
|
|
97
|
+
if (isOpenAIV5(openai)) {
|
|
98
|
+
// Same boundary as retrieveThreadRun — v5 uses named path params.
|
|
99
|
+
const submit = openai.beta.threads.runs.submitToolOutputsStream as {
|
|
100
|
+
(
|
|
101
|
+
...args: unknown[]
|
|
102
|
+
): ReturnType<typeof openai.beta.threads.runs.submitToolOutputsStream>;
|
|
103
|
+
};
|
|
104
|
+
return submit(runId, { thread_id: threadId, ...body });
|
|
105
|
+
}
|
|
106
|
+
return openai.beta.threads.runs.submitToolOutputsStream(
|
|
107
|
+
threadId,
|
|
108
|
+
runId,
|
|
109
|
+
body,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
13
113
|
export function limitMessagesToTokenCount(
|
|
14
114
|
messages: any[],
|
|
15
115
|
tools: any[],
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { HttpAgent } from "@ag-ui/client";
|
|
3
|
+
import {
|
|
4
|
+
resolveAgents,
|
|
5
|
+
type AgentsConfig,
|
|
6
|
+
type AgentFactoryContext,
|
|
7
|
+
} from "../core/runtime";
|
|
8
|
+
import { handleRunAgent } from "../handlers/handle-run";
|
|
9
|
+
import { handleGetRuntimeInfo } from "../handlers/get-runtime-info";
|
|
10
|
+
import { CopilotRuntime } from "../core/runtime";
|
|
11
|
+
|
|
12
|
+
function createMockAgent(name = "test") {
|
|
13
|
+
return new HttpAgent({ url: `https://example.com/${name}` });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createMockRuntime(agents: AgentsConfig) {
|
|
17
|
+
return {
|
|
18
|
+
agents,
|
|
19
|
+
transcriptionService: undefined,
|
|
20
|
+
beforeRequestMiddleware: undefined,
|
|
21
|
+
afterRequestMiddleware: undefined,
|
|
22
|
+
runner: { stop: vi.fn().mockResolvedValue(true) },
|
|
23
|
+
mode: "sse",
|
|
24
|
+
} as unknown as CopilotRuntime;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createMockRequest(headers?: Record<string, string>) {
|
|
28
|
+
return new Request("https://example.com/agent/test/run", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
31
|
+
body: JSON.stringify({ threadId: "thread-1", messages: [], state: {} }),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("resolveAgents", () => {
|
|
36
|
+
it("resolves a static record", async () => {
|
|
37
|
+
const agents = { default: createMockAgent() };
|
|
38
|
+
const result = await resolveAgents(agents);
|
|
39
|
+
expect(result).toBe(agents);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("resolves a Promise", async () => {
|
|
43
|
+
const agents = { default: createMockAgent() };
|
|
44
|
+
const result = await resolveAgents(Promise.resolve(agents));
|
|
45
|
+
expect(result).toEqual(agents);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("calls a factory function with request context", async () => {
|
|
49
|
+
const agent = createMockAgent();
|
|
50
|
+
const factory = vi.fn().mockReturnValue({ default: agent });
|
|
51
|
+
const request = createMockRequest({ "x-tenant-id": "tenant-123" });
|
|
52
|
+
|
|
53
|
+
const result = await resolveAgents(factory, request);
|
|
54
|
+
|
|
55
|
+
expect(factory).toHaveBeenCalledTimes(1);
|
|
56
|
+
expect(factory).toHaveBeenCalledWith({ request });
|
|
57
|
+
expect(result).toEqual({ default: agent });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("calls an async factory function", async () => {
|
|
61
|
+
const agent = createMockAgent();
|
|
62
|
+
const factory = vi.fn().mockResolvedValue({ default: agent });
|
|
63
|
+
const request = createMockRequest();
|
|
64
|
+
|
|
65
|
+
const result = await resolveAgents(factory, request);
|
|
66
|
+
expect(factory).toHaveBeenCalledTimes(1);
|
|
67
|
+
expect(result).toEqual({ default: agent });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("throws when factory is used without a request", async () => {
|
|
71
|
+
const factory = vi.fn().mockReturnValue({ default: createMockAgent() });
|
|
72
|
+
|
|
73
|
+
await expect(resolveAgents(factory)).rejects.toThrow(
|
|
74
|
+
"Agent factory function requires a request context",
|
|
75
|
+
);
|
|
76
|
+
expect(factory).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("factory can read request headers for per-tenant resolution", async () => {
|
|
80
|
+
const tenantAgentA = createMockAgent("tenant-a");
|
|
81
|
+
const tenantAgentB = createMockAgent("tenant-b");
|
|
82
|
+
|
|
83
|
+
const factory = ({ request }: AgentFactoryContext) => {
|
|
84
|
+
const tenantId = request.headers.get("x-tenant-id");
|
|
85
|
+
if (tenantId === "a") return { default: tenantAgentA };
|
|
86
|
+
return { default: tenantAgentB };
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const requestA = createMockRequest({ "x-tenant-id": "a" });
|
|
90
|
+
const requestB = createMockRequest({ "x-tenant-id": "b" });
|
|
91
|
+
|
|
92
|
+
const resultA = await resolveAgents(factory, requestA);
|
|
93
|
+
expect(resultA.default).toBe(tenantAgentA);
|
|
94
|
+
|
|
95
|
+
const resultB = await resolveAgents(factory, requestB);
|
|
96
|
+
expect(resultB.default).toBe(tenantAgentB);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("handleRunAgent with agents factory", () => {
|
|
101
|
+
it("returns 404 when factory returns no matching agent", async () => {
|
|
102
|
+
const factory = vi.fn().mockReturnValue({
|
|
103
|
+
other: createMockAgent("other"),
|
|
104
|
+
});
|
|
105
|
+
const runtime = createMockRuntime(factory);
|
|
106
|
+
const request = createMockRequest();
|
|
107
|
+
|
|
108
|
+
const response = await handleRunAgent({
|
|
109
|
+
runtime,
|
|
110
|
+
request,
|
|
111
|
+
agentId: "nonexistent",
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(response.status).toBe(404);
|
|
115
|
+
expect(factory).toHaveBeenCalledWith({ request });
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("handleGetRuntimeInfo with agents factory", () => {
|
|
120
|
+
it("resolves factory and lists agents", async () => {
|
|
121
|
+
const factory = vi.fn().mockReturnValue({
|
|
122
|
+
support: createMockAgent("support"),
|
|
123
|
+
technical: createMockAgent("technical"),
|
|
124
|
+
});
|
|
125
|
+
const runtime = createMockRuntime(factory);
|
|
126
|
+
const request = createMockRequest();
|
|
127
|
+
|
|
128
|
+
const response = await handleGetRuntimeInfo({ runtime, request });
|
|
129
|
+
|
|
130
|
+
expect(response.status).toBe(200);
|
|
131
|
+
const body = await response.json();
|
|
132
|
+
expect(Object.keys(body.agents)).toContain("support");
|
|
133
|
+
expect(Object.keys(body.agents)).toContain("technical");
|
|
134
|
+
expect(factory).toHaveBeenCalledWith({ request });
|
|
135
|
+
});
|
|
136
|
+
});
|