@copilotkit/runtime 1.57.1-canary.1778272612 → 1.57.2
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/lib/runtime/agent-integrations/langgraph/agent.cjs +2 -0
- package/dist/lib/runtime/agent-integrations/langgraph/agent.cjs.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.cts.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.mts.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs +2 -0
- package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.cjs +2 -0
- package/dist/lib/runtime/copilot-runtime.cjs.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.cts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.d.mts.map +1 -1
- package/dist/lib/runtime/copilot-runtime.mjs +2 -0
- package/dist/lib/runtime/copilot-runtime.mjs.map +1 -1
- package/dist/lib/telemetry-disclosure.cjs +26 -0
- package/dist/lib/telemetry-disclosure.cjs.map +1 -0
- package/dist/lib/telemetry-disclosure.mjs +25 -0
- package/dist/lib/telemetry-disclosure.mjs.map +1 -0
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/v2/runtime/core/runtime.cjs +2 -0
- package/dist/v2/runtime/core/runtime.cjs.map +1 -1
- package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
- package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
- package/dist/v2/runtime/core/runtime.mjs +2 -0
- package/dist/v2/runtime/core/runtime.mjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.cjs +3 -1
- package/dist/v2/runtime/handlers/get-runtime-info.cjs.map +1 -1
- package/dist/v2/runtime/handlers/get-runtime-info.mjs +3 -1
- package/dist/v2/runtime/handlers/get-runtime-info.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-transcribe.cjs +2 -1
- package/dist/v2/runtime/handlers/handle-transcribe.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-transcribe.mjs +2 -1
- package/dist/v2/runtime/handlers/handle-transcribe.mjs.map +1 -1
- package/dist/v2/runtime/telemetry/telemetry-client.cjs +1 -0
- package/dist/v2/runtime/telemetry/telemetry-client.mjs +1 -1
- package/package.json +2 -2
- package/src/lib/__tests__/telemetry-disclosure.test.ts +55 -0
- package/src/lib/runtime/agent-integrations/langgraph/__tests__/run-message-filtering.test.ts +156 -0
- package/src/lib/runtime/agent-integrations/langgraph/agent.ts +24 -10
- package/src/lib/runtime/copilot-runtime.ts +3 -0
- package/src/lib/telemetry-disclosure.ts +53 -0
- package/src/v2/runtime/__tests__/get-runtime-info.test.ts +59 -1
- package/src/v2/runtime/core/runtime.ts +3 -0
- package/src/v2/runtime/handlers/get-runtime-info.ts +6 -10
- package/src/v2/runtime/handlers/handle-transcribe.ts +14 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
type MockInstance,
|
|
9
|
+
} from "vitest";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
_resetRuntimeTelemetryDisclosureForTesting,
|
|
13
|
+
logRuntimeTelemetryDisclosure,
|
|
14
|
+
} from "../telemetry-disclosure";
|
|
15
|
+
|
|
16
|
+
let consoleInfoSpy: MockInstance<typeof console.info>;
|
|
17
|
+
const originalEnv = { ...process.env };
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
_resetRuntimeTelemetryDisclosureForTesting();
|
|
21
|
+
consoleInfoSpy = vi.spyOn(console, "info").mockImplementation(() => {});
|
|
22
|
+
// Clear opt-out env vars so the disclosure can fire by default.
|
|
23
|
+
delete process.env.COPILOTKIT_TELEMETRY_DISABLED;
|
|
24
|
+
delete process.env.DO_NOT_TRACK;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.restoreAllMocks();
|
|
29
|
+
process.env = { ...originalEnv };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("logRuntimeTelemetryDisclosure", () => {
|
|
33
|
+
it("logs once per process", () => {
|
|
34
|
+
logRuntimeTelemetryDisclosure();
|
|
35
|
+
logRuntimeTelemetryDisclosure();
|
|
36
|
+
logRuntimeTelemetryDisclosure();
|
|
37
|
+
|
|
38
|
+
expect(consoleInfoSpy).toHaveBeenCalledTimes(1);
|
|
39
|
+
const [message] = consoleInfoSpy.mock.calls[0]!;
|
|
40
|
+
expect(message).toMatch(/anonymous telemetry/i);
|
|
41
|
+
expect(message).toMatch(/COPILOTKIT_TELEMETRY_DISABLED/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("does not log when COPILOTKIT_TELEMETRY_DISABLED is set", () => {
|
|
45
|
+
process.env.COPILOTKIT_TELEMETRY_DISABLED = "true";
|
|
46
|
+
logRuntimeTelemetryDisclosure();
|
|
47
|
+
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("does not log when DO_NOT_TRACK is set", () => {
|
|
51
|
+
process.env.DO_NOT_TRACK = "1";
|
|
52
|
+
logRuntimeTelemetryDisclosure();
|
|
53
|
+
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { of } from "rxjs";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
import { LangGraphAgent } from "../agent";
|
|
4
|
+
|
|
5
|
+
// REGRESSION: `@ag-ui/langgraph`'s message converter only handles
|
|
6
|
+
// {user, assistant, system, tool} and throws "message role is not
|
|
7
|
+
// supported." on anything else. Reasoning-stream agents (OpenAI
|
|
8
|
+
// Responses API with `reasoning={summary:"detailed"}`) emit AG-UI
|
|
9
|
+
// messages with `role:"reasoning"` that the AG-UI client persists in
|
|
10
|
+
// the thread and replays on the NEXT turn's `input.messages` — which
|
|
11
|
+
// crashes the converter before the model is called.
|
|
12
|
+
//
|
|
13
|
+
// CopilotKit's LangGraphAgent.run subclass strips those reasoning
|
|
14
|
+
// messages from the inbound `input.messages` before delegating to
|
|
15
|
+
// super. This file verifies that filter:
|
|
16
|
+
// - `role:"reasoning"` is dropped.
|
|
17
|
+
// - All other roles (user, assistant, system, tool, plus unknown
|
|
18
|
+
// roles we don't recognize) are preserved in order.
|
|
19
|
+
// - The pre-existing forwardedProps enrichment (`streamSubgraphs`)
|
|
20
|
+
// still applies.
|
|
21
|
+
|
|
22
|
+
function createAgent() {
|
|
23
|
+
return new LangGraphAgent({
|
|
24
|
+
graphId: "test-graph",
|
|
25
|
+
url: "http://localhost:8000",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mock the parent class's `run` method for a single test so we can
|
|
31
|
+
* capture the `input` it receives without actually opening a stream.
|
|
32
|
+
* The harness spies on the prototype TWO levels up because the
|
|
33
|
+
* CopilotKit `LangGraphAgent` extends `@ag-ui/langgraph`'s
|
|
34
|
+
* `LangGraphAgent`. Mirror the pattern from
|
|
35
|
+
* `dispatch-event-filtering.test.ts`'s `withMockedParentMerge`.
|
|
36
|
+
*/
|
|
37
|
+
function withMockedParentRun(agent: LangGraphAgent) {
|
|
38
|
+
const parentProto = Object.getPrototypeOf(Object.getPrototypeOf(agent));
|
|
39
|
+
const calls: any[] = [];
|
|
40
|
+
const spy = vi.spyOn(parentProto, "run").mockImplementation(function (
|
|
41
|
+
this: unknown,
|
|
42
|
+
input: unknown,
|
|
43
|
+
) {
|
|
44
|
+
calls.push(input);
|
|
45
|
+
// Return an empty Observable — none of our assertions consume it,
|
|
46
|
+
// we only care about what was passed in.
|
|
47
|
+
return of();
|
|
48
|
+
});
|
|
49
|
+
return { spy, calls };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function makeInput(messages: Array<{ role: string; id?: string }>) {
|
|
53
|
+
return {
|
|
54
|
+
runId: "run-1",
|
|
55
|
+
threadId: "thread-1",
|
|
56
|
+
messages,
|
|
57
|
+
tools: [],
|
|
58
|
+
context: [],
|
|
59
|
+
state: {},
|
|
60
|
+
forwardedProps: {},
|
|
61
|
+
} as any;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("LangGraphAgent.run reasoning-role message filtering", () => {
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
vi.restoreAllMocks();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("strips role:'reasoning' messages from input before delegating to super.run", () => {
|
|
70
|
+
const agent = createAgent();
|
|
71
|
+
const { calls } = withMockedParentRun(agent);
|
|
72
|
+
|
|
73
|
+
agent.run(
|
|
74
|
+
makeInput([
|
|
75
|
+
{ role: "user", id: "u1" },
|
|
76
|
+
{ role: "reasoning", id: "r1" },
|
|
77
|
+
{ role: "assistant", id: "a1" },
|
|
78
|
+
{ role: "tool", id: "t1" },
|
|
79
|
+
{ role: "reasoning", id: "r2" },
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect(calls).toHaveLength(1);
|
|
84
|
+
expect(calls[0].messages.map((m: { id: string }) => m.id)).toEqual([
|
|
85
|
+
"u1",
|
|
86
|
+
"a1",
|
|
87
|
+
"t1",
|
|
88
|
+
]);
|
|
89
|
+
expect(
|
|
90
|
+
calls[0].messages.some((m: { role: string }) => m.role === "reasoning"),
|
|
91
|
+
).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("preserves user/assistant/system/tool messages in their original order", () => {
|
|
95
|
+
const agent = createAgent();
|
|
96
|
+
const { calls } = withMockedParentRun(agent);
|
|
97
|
+
|
|
98
|
+
agent.run(
|
|
99
|
+
makeInput([
|
|
100
|
+
{ role: "system", id: "s1" },
|
|
101
|
+
{ role: "user", id: "u1" },
|
|
102
|
+
{ role: "assistant", id: "a1" },
|
|
103
|
+
{ role: "tool", id: "t1" },
|
|
104
|
+
{ role: "user", id: "u2" },
|
|
105
|
+
]),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(calls[0].messages.map((m: { id: string }) => m.id)).toEqual([
|
|
109
|
+
"s1",
|
|
110
|
+
"u1",
|
|
111
|
+
"a1",
|
|
112
|
+
"t1",
|
|
113
|
+
"u2",
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("tolerates an empty messages array without throwing", () => {
|
|
118
|
+
const agent = createAgent();
|
|
119
|
+
const { calls } = withMockedParentRun(agent);
|
|
120
|
+
|
|
121
|
+
agent.run(makeInput([]));
|
|
122
|
+
|
|
123
|
+
expect(calls[0].messages).toEqual([]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("tolerates omitted messages (defaults to empty array)", () => {
|
|
127
|
+
const agent = createAgent();
|
|
128
|
+
const { calls } = withMockedParentRun(agent);
|
|
129
|
+
|
|
130
|
+
const input = makeInput([{ role: "user", id: "u1" }]);
|
|
131
|
+
delete input.messages;
|
|
132
|
+
agent.run(input);
|
|
133
|
+
|
|
134
|
+
expect(calls[0].messages).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("preserves the pre-existing forwardedProps.streamSubgraphs default", () => {
|
|
138
|
+
const agent = createAgent();
|
|
139
|
+
const { calls } = withMockedParentRun(agent);
|
|
140
|
+
|
|
141
|
+
agent.run(makeInput([{ role: "user", id: "u1" }]));
|
|
142
|
+
|
|
143
|
+
expect(calls[0].forwardedProps).toMatchObject({ streamSubgraphs: true });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("respects an explicit forwardedProps.streamSubgraphs=false", () => {
|
|
147
|
+
const agent = createAgent();
|
|
148
|
+
const { calls } = withMockedParentRun(agent);
|
|
149
|
+
|
|
150
|
+
const input = makeInput([{ role: "user", id: "u1" }]);
|
|
151
|
+
input.forwardedProps = { streamSubgraphs: false };
|
|
152
|
+
agent.run(input);
|
|
153
|
+
|
|
154
|
+
expect(calls[0].forwardedProps.streamSubgraphs).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Observable } from "rxjs";
|
|
2
|
+
import { map } from "rxjs";
|
|
2
3
|
import { LangGraphEventTypes } from "../../../../agents/langgraph/events";
|
|
3
|
-
import { BaseEvent, RawEvent } from "@ag-ui/core";
|
|
4
|
+
import type { BaseEvent, RawEvent } from "@ag-ui/core";
|
|
5
|
+
import type {
|
|
6
|
+
ProcessedEvents,
|
|
7
|
+
SchemaKeys,
|
|
8
|
+
StateEnrichment,
|
|
9
|
+
} from "@ag-ui/langgraph";
|
|
4
10
|
import {
|
|
5
11
|
LangGraphAgent as AGUILangGraphAgent,
|
|
6
12
|
LangGraphHttpAgent,
|
|
7
13
|
type LangGraphAgentConfig,
|
|
8
|
-
ProcessedEvents,
|
|
9
|
-
SchemaKeys,
|
|
10
14
|
type State,
|
|
11
|
-
StateEnrichment,
|
|
12
15
|
} from "@ag-ui/langgraph";
|
|
13
|
-
import { Message as LangGraphMessage } from "@langchain/langgraph-sdk/dist/types.messages";
|
|
14
|
-
import { ThreadState } from "@langchain/langgraph-sdk";
|
|
16
|
+
import type { Message as LangGraphMessage } from "@langchain/langgraph-sdk/dist/types.messages";
|
|
17
|
+
import type { ThreadState } from "@langchain/langgraph-sdk";
|
|
15
18
|
|
|
16
19
|
interface CopilotKitStateEnrichment {
|
|
17
20
|
copilotkit: {
|
|
@@ -20,15 +23,16 @@ interface CopilotKitStateEnrichment {
|
|
|
20
23
|
};
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
import { RunAgentInput,
|
|
26
|
+
import type { RunAgentInput, CustomEvent } from "@ag-ui/client";
|
|
27
|
+
import { EventType } from "@ag-ui/client";
|
|
24
28
|
|
|
25
29
|
// Import and re-export from separate file to maintain API compatibility
|
|
26
|
-
import {
|
|
27
|
-
CustomEventNames,
|
|
30
|
+
import type {
|
|
28
31
|
TextMessageEvents,
|
|
29
32
|
ToolCallEvents,
|
|
30
33
|
PredictStateTool,
|
|
31
34
|
} from "./consts";
|
|
35
|
+
import { CustomEventNames } from "./consts";
|
|
32
36
|
export { CustomEventNames };
|
|
33
37
|
|
|
34
38
|
export class LangGraphAgent extends AGUILangGraphAgent {
|
|
@@ -154,8 +158,18 @@ export class LangGraphAgent extends AGUILangGraphAgent {
|
|
|
154
158
|
|
|
155
159
|
// @ts-ignore
|
|
156
160
|
run(input: RunAgentInput): Observable<BaseEvent> {
|
|
161
|
+
// @ag-ui/langgraph's message converter throws "message role is not
|
|
162
|
+
// supported." on any role it doesn't enumerate (user|assistant|system|tool).
|
|
163
|
+
// Reasoning-stream agents (e.g. OpenAI Responses API with reasoning summaries)
|
|
164
|
+
// emit AG-UI messages with role:"reasoning" that the client then replays on
|
|
165
|
+
// subsequent turns; without this filter the second turn crashes before the
|
|
166
|
+
// model is ever called.
|
|
167
|
+
const messages = (input.messages ?? []).filter(
|
|
168
|
+
(m: { role?: string }) => m?.role !== "reasoning",
|
|
169
|
+
);
|
|
157
170
|
const enrichedInput = {
|
|
158
171
|
...input,
|
|
172
|
+
messages,
|
|
159
173
|
forwardedProps: {
|
|
160
174
|
...input.forwardedProps,
|
|
161
175
|
streamSubgraphs: input.forwardedProps?.streamSubgraphs ?? true,
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
export type { AgentsConfig, AgentsFactory, AgentFactoryContext };
|
|
46
46
|
import { TelemetryAgentRunner } from "./telemetry-agent-runner";
|
|
47
47
|
import telemetry from "../telemetry-client";
|
|
48
|
+
import { logRuntimeTelemetryDisclosure } from "../telemetry-disclosure";
|
|
48
49
|
|
|
49
50
|
import type { MessageInput } from "../../graphql/inputs/message.input";
|
|
50
51
|
import type { Message } from "../../graphql/types/converted";
|
|
@@ -356,6 +357,8 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
356
357
|
params?: CopilotRuntimeConstructorParams<T> &
|
|
357
358
|
PartialBy<CopilotRuntimeOptions, "agents">,
|
|
358
359
|
) {
|
|
360
|
+
logRuntimeTelemetryDisclosure();
|
|
361
|
+
|
|
359
362
|
const agents = params?.agents ?? {};
|
|
360
363
|
const endpointAgents = this.assignEndpointsToAgents(
|
|
361
364
|
params?.remoteEndpoints ?? [],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Runtime-side anonymous telemetry disclosure log.
|
|
2
|
+
//
|
|
3
|
+
// The runtime has shipped anonymous telemetry for some time (see
|
|
4
|
+
// `packages/shared/src/telemetry/telemetry-client.ts`). This file just
|
|
5
|
+
// surfaces a one-line pointer to the opt-out docs on first
|
|
6
|
+
// instantiation so operators don't have to dig through the docs site
|
|
7
|
+
// to discover the existing behavior. Pairs with the inspector's
|
|
8
|
+
// first-run console disclosure for a consistent operator-facing
|
|
9
|
+
// surface.
|
|
10
|
+
//
|
|
11
|
+
// Fires at most once per process — runtime instances may be constructed
|
|
12
|
+
// multiple times (tests, hot-reload), but the disclosure is informational
|
|
13
|
+
// and a single line is enough.
|
|
14
|
+
|
|
15
|
+
// Canonical telemetry docs page on main.
|
|
16
|
+
// Mirror constant: packages/web-inspector/src/lib/telemetry.ts
|
|
17
|
+
const TELEMETRY_DOCS_URL = "https://docs.copilotkit.ai/telemetry";
|
|
18
|
+
|
|
19
|
+
function isTelemetryDisabled(): boolean {
|
|
20
|
+
const env = process.env as Record<string, string | undefined>;
|
|
21
|
+
return (
|
|
22
|
+
env.COPILOTKIT_TELEMETRY_DISABLED === "true" ||
|
|
23
|
+
env.COPILOTKIT_TELEMETRY_DISABLED === "1" ||
|
|
24
|
+
env.DO_NOT_TRACK === "true" ||
|
|
25
|
+
env.DO_NOT_TRACK === "1"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let disclosureLogged = false;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Logs a one-line console.info about anonymous telemetry on runtime
|
|
33
|
+
* startup. No-op when telemetry is disabled via `COPILOTKIT_TELEMETRY_DISABLED`
|
|
34
|
+
* or `DO_NOT_TRACK`, or when already logged once in this process.
|
|
35
|
+
*
|
|
36
|
+
* Idempotent — safe to call from multiple constructor paths.
|
|
37
|
+
*/
|
|
38
|
+
export function logRuntimeTelemetryDisclosure(): void {
|
|
39
|
+
if (disclosureLogged) return;
|
|
40
|
+
if (isTelemetryDisabled()) return;
|
|
41
|
+
disclosureLogged = true;
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.info(
|
|
44
|
+
`[CopilotKit Runtime] anonymous telemetry enabled — see ${TELEMETRY_DOCS_URL} to opt out (set COPILOTKIT_TELEMETRY_DISABLED=true).`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Test-only reset hook so the once-per-process guard doesn't leak between
|
|
49
|
+
// test cases. Not part of the public package surface — used by the runtime
|
|
50
|
+
// disclosure tests.
|
|
51
|
+
export function _resetRuntimeTelemetryDisclosureForTesting(): void {
|
|
52
|
+
disclosureLogged = false;
|
|
53
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { handleGetRuntimeInfo } from "../handlers/get-runtime-info";
|
|
2
2
|
import { CopilotRuntime } from "../core/runtime";
|
|
3
3
|
import { TranscriptionService } from "../transcription-service/transcription-service";
|
|
4
|
-
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
5
5
|
import type { AbstractAgent } from "@ag-ui/client";
|
|
6
6
|
|
|
7
7
|
// Mock transcription service
|
|
@@ -35,6 +35,7 @@ describe("handleGetRuntimeInfo", () => {
|
|
|
35
35
|
mode: "sse",
|
|
36
36
|
a2uiEnabled: false,
|
|
37
37
|
openGenerativeUIEnabled: false,
|
|
38
|
+
telemetryDisabled: false,
|
|
38
39
|
});
|
|
39
40
|
});
|
|
40
41
|
|
|
@@ -60,6 +61,7 @@ describe("handleGetRuntimeInfo", () => {
|
|
|
60
61
|
mode: "sse",
|
|
61
62
|
a2uiEnabled: false,
|
|
62
63
|
openGenerativeUIEnabled: false,
|
|
64
|
+
telemetryDisabled: false,
|
|
63
65
|
});
|
|
64
66
|
});
|
|
65
67
|
|
|
@@ -97,6 +99,7 @@ describe("handleGetRuntimeInfo", () => {
|
|
|
97
99
|
mode: "sse",
|
|
98
100
|
a2uiEnabled: false,
|
|
99
101
|
openGenerativeUIEnabled: false,
|
|
102
|
+
telemetryDisabled: false,
|
|
100
103
|
});
|
|
101
104
|
});
|
|
102
105
|
|
|
@@ -250,6 +253,61 @@ describe("handleGetRuntimeInfo", () => {
|
|
|
250
253
|
warnSpy.mockRestore();
|
|
251
254
|
});
|
|
252
255
|
|
|
256
|
+
describe("telemetryDisabled", () => {
|
|
257
|
+
beforeEach(() => {
|
|
258
|
+
delete process.env.COPILOTKIT_TELEMETRY_DISABLED;
|
|
259
|
+
delete process.env.DO_NOT_TRACK;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
afterEach(() => {
|
|
263
|
+
delete process.env.COPILOTKIT_TELEMETRY_DISABLED;
|
|
264
|
+
delete process.env.DO_NOT_TRACK;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("returns telemetryDisabled: false when env var is not set", async () => {
|
|
268
|
+
const runtime = new CopilotRuntime({ agents: {} });
|
|
269
|
+
const response = await handleGetRuntimeInfo({
|
|
270
|
+
runtime,
|
|
271
|
+
request: mockRequest,
|
|
272
|
+
});
|
|
273
|
+
const data = await response.json();
|
|
274
|
+
expect(data.telemetryDisabled).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("returns telemetryDisabled: true when COPILOTKIT_TELEMETRY_DISABLED=true", async () => {
|
|
278
|
+
process.env.COPILOTKIT_TELEMETRY_DISABLED = "true";
|
|
279
|
+
const runtime = new CopilotRuntime({ agents: {} });
|
|
280
|
+
const response = await handleGetRuntimeInfo({
|
|
281
|
+
runtime,
|
|
282
|
+
request: mockRequest,
|
|
283
|
+
});
|
|
284
|
+
const data = await response.json();
|
|
285
|
+
expect(data.telemetryDisabled).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("returns telemetryDisabled: true when COPILOTKIT_TELEMETRY_DISABLED=1", async () => {
|
|
289
|
+
process.env.COPILOTKIT_TELEMETRY_DISABLED = "1";
|
|
290
|
+
const runtime = new CopilotRuntime({ agents: {} });
|
|
291
|
+
const response = await handleGetRuntimeInfo({
|
|
292
|
+
runtime,
|
|
293
|
+
request: mockRequest,
|
|
294
|
+
});
|
|
295
|
+
const data = await response.json();
|
|
296
|
+
expect(data.telemetryDisabled).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("returns telemetryDisabled: true when DO_NOT_TRACK=1", async () => {
|
|
300
|
+
process.env.DO_NOT_TRACK = "1";
|
|
301
|
+
const runtime = new CopilotRuntime({ agents: {} });
|
|
302
|
+
const response = await handleGetRuntimeInfo({
|
|
303
|
+
runtime,
|
|
304
|
+
request: mockRequest,
|
|
305
|
+
});
|
|
306
|
+
const data = await response.json();
|
|
307
|
+
expect(data.telemetryDisabled).toBe(true);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
253
311
|
it("should return 500 error when runtime.agents throws an error", async () => {
|
|
254
312
|
const runtime = {
|
|
255
313
|
get agents(): Record<string, AbstractAgent> {
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
AfterRequestMiddleware,
|
|
24
24
|
} from "./middleware";
|
|
25
25
|
import { createLogger, type CopilotRuntimeLogger } from "../../../lib/logger";
|
|
26
|
+
import { logRuntimeTelemetryDisclosure } from "../../../lib/telemetry-disclosure";
|
|
26
27
|
import { TranscriptionService } from "../transcription-service/transcription-service";
|
|
27
28
|
import { DebugEventBus } from "./debug-event-bus";
|
|
28
29
|
import { AgentRunner } from "../runner/agent-runner";
|
|
@@ -229,6 +230,8 @@ abstract class BaseCopilotRuntime implements CopilotRuntimeLike {
|
|
|
229
230
|
abstract readonly mode: RuntimeMode;
|
|
230
231
|
|
|
231
232
|
constructor(options: BaseCopilotRuntimeOptions, runner: AgentRunner) {
|
|
233
|
+
logRuntimeTelemetryDisclosure();
|
|
234
|
+
|
|
232
235
|
const {
|
|
233
236
|
agents,
|
|
234
237
|
transcriptionService,
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
import type { AgentCapabilities } from "@ag-ui/core";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../core/runtime";
|
|
7
|
-
import {
|
|
8
|
-
AgentDescription,
|
|
9
|
-
RuntimeInfo,
|
|
10
|
-
type RuntimeLicenseStatus,
|
|
11
|
-
} from "@copilotkit/shared";
|
|
2
|
+
import type { CopilotRuntimeLike } from "../core/runtime";
|
|
3
|
+
import { isIntelligenceRuntime, resolveAgents } from "../core/runtime";
|
|
4
|
+
import type { AgentDescription, RuntimeInfo } from "@copilotkit/shared";
|
|
5
|
+
import { type RuntimeLicenseStatus } from "@copilotkit/shared";
|
|
12
6
|
import { VERSION } from "../core/runtime";
|
|
7
|
+
import { isTelemetryDisabled } from "../telemetry/telemetry-client";
|
|
13
8
|
|
|
14
9
|
function resolveLicenseStatus(
|
|
15
10
|
runtime: CopilotRuntimeLike,
|
|
@@ -84,6 +79,7 @@ export async function handleGetRuntimeInfo({
|
|
|
84
79
|
...(isIntelligenceRuntime(runtime)
|
|
85
80
|
? { licenseStatus: resolveLicenseStatus(runtime) }
|
|
86
81
|
: {}),
|
|
82
|
+
telemetryDisabled: isTelemetryDisabled(),
|
|
87
83
|
};
|
|
88
84
|
|
|
89
85
|
return new Response(JSON.stringify(runtimeInfo), {
|
|
@@ -87,7 +87,7 @@ async function extractAudioFromFormData(
|
|
|
87
87
|
request: Request,
|
|
88
88
|
): Promise<{ file: File } | { error: Response }> {
|
|
89
89
|
const formData = await request.formData();
|
|
90
|
-
|
|
90
|
+
let audioFile = formData.get("audio") as File | null;
|
|
91
91
|
|
|
92
92
|
if (!audioFile || !(audioFile instanceof File)) {
|
|
93
93
|
const err = TranscriptionErrors.invalidRequest(
|
|
@@ -104,6 +104,19 @@ async function extractAudioFromFormData(
|
|
|
104
104
|
return { error: createErrorResponse(err) };
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Browser MediaRecorder Blobs often arrive at the server with their
|
|
108
|
+
// `type` field empty (and `application/octet-stream` is also accepted by
|
|
109
|
+
// isValidAudioType for compatibility). OpenAI Whisper rejects files
|
|
110
|
+
// without a recognizable MIME / extension with a 502 "Invalid file
|
|
111
|
+
// format" — even though the bytes themselves are valid WebM Opus.
|
|
112
|
+
// Stamp `audio/webm` (the standard MediaRecorder default in Chromium /
|
|
113
|
+
// Firefox / Safari) so Whisper can pick the right decoder.
|
|
114
|
+
if (!audioFile.type || audioFile.type === "application/octet-stream") {
|
|
115
|
+
audioFile = new File([audioFile], audioFile.name || "recording.webm", {
|
|
116
|
+
type: "audio/webm",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
return { file: audioFile };
|
|
108
121
|
}
|
|
109
122
|
|