@copilotkitnext/runtime 1.54.0 → 1.54.1-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/dist/endpoints/express-single.cjs.map +1 -1
- package/dist/endpoints/express-single.d.cts +2 -2
- package/dist/endpoints/express-single.d.cts.map +1 -1
- package/dist/endpoints/express-single.d.mts +2 -2
- package/dist/endpoints/express-single.d.mts.map +1 -1
- package/dist/endpoints/express-single.mjs.map +1 -1
- package/dist/endpoints/express.cjs +38 -0
- package/dist/endpoints/express.cjs.map +1 -1
- package/dist/endpoints/express.d.cts +2 -2
- package/dist/endpoints/express.d.cts.map +1 -1
- package/dist/endpoints/express.d.mts +2 -2
- package/dist/endpoints/express.d.mts.map +1 -1
- package/dist/endpoints/express.mjs +38 -0
- package/dist/endpoints/express.mjs.map +1 -1
- package/dist/endpoints/hono-single.cjs.map +1 -1
- package/dist/endpoints/hono-single.d.cts +2 -2
- package/dist/endpoints/hono-single.d.cts.map +1 -1
- package/dist/endpoints/hono-single.d.mts +2 -2
- package/dist/endpoints/hono-single.d.mts.map +1 -1
- package/dist/endpoints/hono-single.mjs.map +1 -1
- package/dist/endpoints/hono.cjs +83 -0
- package/dist/endpoints/hono.cjs.map +1 -1
- package/dist/endpoints/hono.d.cts +3 -3
- package/dist/endpoints/hono.d.cts.map +1 -1
- package/dist/endpoints/hono.d.mts +3 -3
- package/dist/endpoints/hono.d.mts.map +1 -1
- package/dist/endpoints/hono.mjs +83 -0
- package/dist/endpoints/hono.mjs.map +1 -1
- package/dist/handlers/get-runtime-info.cjs +2 -0
- package/dist/handlers/get-runtime-info.cjs.map +1 -1
- package/dist/handlers/get-runtime-info.mjs +3 -1
- package/dist/handlers/get-runtime-info.mjs.map +1 -1
- package/dist/handlers/handle-connect.cjs +18 -80
- package/dist/handlers/handle-connect.cjs.map +1 -1
- package/dist/handlers/handle-connect.mjs +18 -79
- package/dist/handlers/handle-connect.mjs.map +1 -1
- package/dist/handlers/handle-run.cjs +26 -98
- package/dist/handlers/handle-run.cjs.map +1 -1
- package/dist/handlers/handle-run.mjs +26 -97
- package/dist/handlers/handle-run.mjs.map +1 -1
- package/dist/handlers/handle-stop.cjs.map +1 -1
- package/dist/handlers/handle-stop.mjs.map +1 -1
- package/dist/handlers/handle-threads.cjs +1 -0
- package/dist/handlers/handle-threads.mjs +3 -0
- package/dist/handlers/handle-transcribe.cjs.map +1 -1
- package/dist/handlers/handle-transcribe.mjs.map +1 -1
- package/dist/handlers/intelligence/connect.cjs +28 -0
- package/dist/handlers/intelligence/connect.cjs.map +1 -0
- package/dist/handlers/intelligence/connect.mjs +28 -0
- package/dist/handlers/intelligence/connect.mjs.map +1 -0
- package/dist/handlers/intelligence/run.cjs +77 -0
- package/dist/handlers/intelligence/run.cjs.map +1 -0
- package/dist/handlers/intelligence/run.mjs +76 -0
- package/dist/handlers/intelligence/run.mjs.map +1 -0
- package/dist/handlers/intelligence/thread-names.cjs +144 -0
- package/dist/handlers/intelligence/thread-names.cjs.map +1 -0
- package/dist/handlers/intelligence/thread-names.mjs +143 -0
- package/dist/handlers/intelligence/thread-names.mjs.map +1 -0
- package/dist/handlers/intelligence/threads.cjs +133 -0
- package/dist/handlers/intelligence/threads.cjs.map +1 -0
- package/dist/handlers/intelligence/threads.mjs +128 -0
- package/dist/handlers/intelligence/threads.mjs.map +1 -0
- package/dist/handlers/shared/agent-utils.cjs +73 -0
- package/dist/handlers/shared/agent-utils.cjs.map +1 -0
- package/dist/handlers/shared/agent-utils.mjs +69 -0
- package/dist/handlers/shared/agent-utils.mjs.map +1 -0
- package/dist/handlers/shared/intelligence-utils.cjs +20 -0
- package/dist/handlers/shared/intelligence-utils.cjs.map +1 -0
- package/dist/handlers/shared/intelligence-utils.mjs +19 -0
- package/dist/handlers/shared/intelligence-utils.mjs.map +1 -0
- package/dist/handlers/shared/json-response.cjs +7 -0
- package/dist/handlers/shared/json-response.cjs.map +1 -0
- package/dist/handlers/shared/json-response.mjs +6 -0
- package/dist/handlers/shared/json-response.mjs.map +1 -0
- package/dist/handlers/shared/sse-response.cjs +63 -0
- package/dist/handlers/shared/sse-response.cjs.map +1 -0
- package/dist/handlers/shared/sse-response.mjs +62 -0
- package/dist/handlers/shared/sse-response.mjs.map +1 -0
- package/dist/handlers/sse/connect.cjs +17 -0
- package/dist/handlers/sse/connect.cjs.map +1 -0
- package/dist/handlers/sse/connect.mjs +17 -0
- package/dist/handlers/sse/connect.mjs.map +1 -0
- package/dist/handlers/sse/run.cjs +17 -0
- package/dist/handlers/sse/run.cjs.map +1 -0
- package/dist/handlers/sse/run.mjs +17 -0
- package/dist/handlers/sse/run.mjs.map +1 -0
- package/dist/index.cjs +8 -2
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +5 -3
- package/dist/intelligence-platform/client.cjs +318 -0
- package/dist/intelligence-platform/client.cjs.map +1 -0
- package/dist/intelligence-platform/client.d.cts +327 -0
- package/dist/intelligence-platform/client.d.cts.map +1 -0
- package/dist/intelligence-platform/client.d.mts +327 -0
- package/dist/intelligence-platform/client.d.mts.map +1 -0
- package/dist/intelligence-platform/client.mjs +316 -0
- package/dist/intelligence-platform/client.mjs.map +1 -0
- package/dist/intelligence-platform/index.cjs +1 -0
- package/dist/intelligence-platform/index.d.mts +1 -0
- package/dist/intelligence-platform/index.mjs +3 -0
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +3 -3
- package/dist/middleware.d.cts.map +1 -1
- package/dist/middleware.d.mts +3 -3
- package/dist/middleware.d.mts.map +1 -1
- package/dist/middleware.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/runner/agent-runner.cjs.map +1 -1
- package/dist/runner/agent-runner.d.cts +4 -1
- package/dist/runner/agent-runner.d.cts.map +1 -1
- package/dist/runner/agent-runner.d.mts +4 -1
- package/dist/runner/agent-runner.d.mts.map +1 -1
- package/dist/runner/agent-runner.mjs.map +1 -1
- package/dist/runner/in-memory.cjs +1 -1
- package/dist/runner/in-memory.mjs +1 -1
- package/dist/runner/intelligence.cjs +87 -24
- package/dist/runner/intelligence.cjs.map +1 -1
- package/dist/runner/intelligence.d.cts +8 -3
- package/dist/runner/intelligence.d.cts.map +1 -1
- package/dist/runner/intelligence.d.mts +8 -3
- package/dist/runner/intelligence.d.mts.map +1 -1
- package/dist/runner/intelligence.mjs +87 -24
- package/dist/runner/intelligence.mjs.map +1 -1
- package/dist/runtime.cjs +76 -6
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +71 -21
- package/dist/runtime.d.cts.map +1 -1
- package/dist/runtime.d.mts +72 -21
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +73 -7
- package/dist/runtime.mjs.map +1 -1
- package/package.json +5 -5
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_agent_runner = require('./agent-runner.cjs');
|
|
3
|
+
let _copilotkitnext_shared = require("@copilotkitnext/shared");
|
|
3
4
|
let rxjs = require("rxjs");
|
|
4
5
|
let _ag_ui_client = require("@ag-ui/client");
|
|
5
|
-
let _copilotkitnext_shared = require("@copilotkitnext/shared");
|
|
6
6
|
let rxjs_operators = require("rxjs/operators");
|
|
7
7
|
let phoenix = require("phoenix");
|
|
8
|
+
let node_crypto = require("node:crypto");
|
|
8
9
|
|
|
9
10
|
//#region src/runner/intelligence.ts
|
|
10
11
|
var IntelligenceAgentRunner = class extends require_agent_runner.AgentRunner {
|
|
@@ -37,26 +38,56 @@ var IntelligenceAgentRunner = class extends require_agent_runner.AgentRunner {
|
|
|
37
38
|
*/
|
|
38
39
|
createSocket() {
|
|
39
40
|
const socket = new phoenix.Socket(this.options.url, {
|
|
40
|
-
|
|
41
|
+
...this.options.authToken ? { authToken: this.options.authToken } : {},
|
|
41
42
|
reconnectAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(100, 1e4),
|
|
42
43
|
rejoinAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(1e3, 3e4)
|
|
43
44
|
});
|
|
44
45
|
socket.connect();
|
|
45
46
|
return socket;
|
|
46
47
|
}
|
|
48
|
+
createRunnerEventPayload(event, request, state) {
|
|
49
|
+
const payload = { ...this.stampRunnerMetadata(event, state) };
|
|
50
|
+
payload.thread_id ??= request.threadId;
|
|
51
|
+
const runId = payload.runId ?? payload.run_id ?? request.input.runId;
|
|
52
|
+
if (runId) payload.run_id = runId;
|
|
53
|
+
return payload;
|
|
54
|
+
}
|
|
55
|
+
stampRunnerMetadata(event, state) {
|
|
56
|
+
const eventRecord = event;
|
|
57
|
+
const existingMetadata = eventRecord.metadata ?? {};
|
|
58
|
+
const hasEventId = typeof existingMetadata.cpki_event_id === "string";
|
|
59
|
+
const hasEventSeq = typeof existingMetadata.cpki_event_seq === "number";
|
|
60
|
+
if (hasEventId && hasEventSeq) {
|
|
61
|
+
const eventSeq = existingMetadata.cpki_event_seq;
|
|
62
|
+
state.nextEventSeq = Math.max(state.nextEventSeq, eventSeq + 1);
|
|
63
|
+
return eventRecord;
|
|
64
|
+
}
|
|
65
|
+
const eventSeq = state.nextEventSeq++;
|
|
66
|
+
return {
|
|
67
|
+
...eventRecord,
|
|
68
|
+
metadata: {
|
|
69
|
+
...existingMetadata,
|
|
70
|
+
cpki_event_id: typeof existingMetadata.cpki_event_id === "string" ? existingMetadata.cpki_event_id : (0, node_crypto.randomUUID)(),
|
|
71
|
+
cpki_event_seq: eventSeq
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
47
75
|
run(request) {
|
|
48
|
-
const { threadId, agent, input } = request;
|
|
76
|
+
const { threadId, agent, input, joinCode } = request;
|
|
49
77
|
if (this.threads.get(threadId)?.isRunning) throw new Error("Thread already running");
|
|
50
78
|
return new rxjs.Observable((observer) => {
|
|
51
79
|
const socket = this.createSocket();
|
|
52
|
-
const
|
|
80
|
+
const channelTopic = joinCode ?? threadId;
|
|
81
|
+
const channel = socket.channel(`ingestion:${channelTopic}`, { runId: input.runId });
|
|
53
82
|
const state = {
|
|
54
83
|
socket,
|
|
55
84
|
channel,
|
|
56
85
|
isRunning: true,
|
|
57
86
|
stopRequested: false,
|
|
58
87
|
agent,
|
|
59
|
-
currentEvents: []
|
|
88
|
+
currentEvents: [],
|
|
89
|
+
nextEventSeq: 1,
|
|
90
|
+
hasRunStarted: false
|
|
60
91
|
};
|
|
61
92
|
this.threads.set(threadId, state);
|
|
62
93
|
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
@@ -105,8 +136,8 @@ var IntelligenceAgentRunner = class extends require_agent_runner.AgentRunner {
|
|
|
105
136
|
const { threadId } = request;
|
|
106
137
|
return new rxjs.Observable((observer) => {
|
|
107
138
|
const socket = this.createSocket();
|
|
108
|
-
const channel = socket.channel(`
|
|
109
|
-
channel.on(
|
|
139
|
+
const channel = socket.channel(`thread:${threadId}`);
|
|
140
|
+
channel.on("ag_ui_event", (payload) => {
|
|
110
141
|
observer.next(payload);
|
|
111
142
|
if (payload.type === _ag_ui_client.EventType.RUN_FINISHED || payload.type === _ag_ui_client.EventType.RUN_ERROR) observer.complete();
|
|
112
143
|
});
|
|
@@ -114,13 +145,7 @@ var IntelligenceAgentRunner = class extends require_agent_runner.AgentRunner {
|
|
|
114
145
|
channel.leave();
|
|
115
146
|
socket.disconnect();
|
|
116
147
|
};
|
|
117
|
-
channel.join().receive("ok", () => {
|
|
118
|
-
channel.push(_ag_ui_client.EventType.CUSTOM, {
|
|
119
|
-
type: _ag_ui_client.EventType.CUSTOM,
|
|
120
|
-
name: "connect",
|
|
121
|
-
value: { threadId }
|
|
122
|
-
});
|
|
123
|
-
}).receive("error", (resp) => {
|
|
148
|
+
channel.join().receive("ok", () => void 0).receive("error", (resp) => {
|
|
124
149
|
observer.error(/* @__PURE__ */ new Error(`Failed to join channel: ${JSON.stringify(resp)}`));
|
|
125
150
|
cleanup();
|
|
126
151
|
}).receive("timeout", () => {
|
|
@@ -147,34 +172,72 @@ var IntelligenceAgentRunner = class extends require_agent_runner.AgentRunner {
|
|
|
147
172
|
}
|
|
148
173
|
executeAgentRun(request, state, threadId) {
|
|
149
174
|
const { currentEvents, channel } = state;
|
|
175
|
+
const pushCanonicalEvent = (event) => {
|
|
176
|
+
const canonicalEvent = this.stampRunnerMetadata(event, state);
|
|
177
|
+
currentEvents.push(canonicalEvent);
|
|
178
|
+
if (canonicalEvent.type === _ag_ui_client.EventType.RUN_STARTED) state.hasRunStarted = true;
|
|
179
|
+
channel.push("event", this.createRunnerEventPayload(canonicalEvent, request, state));
|
|
180
|
+
};
|
|
181
|
+
const getPersistedInputMessages = () => request.persistedInputMessages ?? request.input.messages;
|
|
182
|
+
const buildRunStartedEvent = (source) => {
|
|
183
|
+
const baseInput = source?.input ?? request.input;
|
|
184
|
+
const persistedInputMessages = getPersistedInputMessages();
|
|
185
|
+
return {
|
|
186
|
+
...source ?? {
|
|
187
|
+
type: _ag_ui_client.EventType.RUN_STARTED,
|
|
188
|
+
threadId: request.threadId,
|
|
189
|
+
runId: request.input.runId
|
|
190
|
+
},
|
|
191
|
+
input: {
|
|
192
|
+
...baseInput,
|
|
193
|
+
...persistedInputMessages !== void 0 ? { messages: persistedInputMessages } : {}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
const ensureRunStarted = () => {
|
|
198
|
+
if (!state.hasRunStarted) {
|
|
199
|
+
state.hasRunStarted = true;
|
|
200
|
+
pushCanonicalEvent(buildRunStartedEvent());
|
|
201
|
+
}
|
|
202
|
+
};
|
|
150
203
|
return (0, rxjs.from)(request.agent.runAgent(request.input, { onEvent: ({ event }) => {
|
|
151
|
-
|
|
152
|
-
|
|
204
|
+
if (event.type === _ag_ui_client.EventType.RUN_STARTED) {
|
|
205
|
+
pushCanonicalEvent(buildRunStartedEvent(event));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
ensureRunStarted();
|
|
209
|
+
pushCanonicalEvent(event);
|
|
153
210
|
} })).pipe((0, rxjs_operators.catchError)((error) => {
|
|
154
|
-
|
|
211
|
+
ensureRunStarted();
|
|
212
|
+
pushCanonicalEvent({
|
|
155
213
|
type: _ag_ui_client.EventType.RUN_ERROR,
|
|
156
214
|
message: error instanceof Error ? error.message : String(error)
|
|
157
|
-
};
|
|
158
|
-
currentEvents.push(errorEvent);
|
|
159
|
-
channel.push(_copilotkitnext_shared.AG_UI_CHANNEL_EVENT, errorEvent);
|
|
215
|
+
});
|
|
160
216
|
return rxjs.EMPTY;
|
|
161
217
|
}), (0, rxjs_operators.finalize)(() => {
|
|
218
|
+
ensureRunStarted();
|
|
162
219
|
const appended = (0, _copilotkitnext_shared.finalizeRunEvents)(currentEvents, { stopRequested: state.stopRequested });
|
|
163
|
-
for (const event of appended) channel.push(
|
|
220
|
+
for (const event of appended) channel.push("event", this.createRunnerEventPayload(event, request, state));
|
|
164
221
|
this.removeThread(threadId);
|
|
165
222
|
}));
|
|
166
223
|
}
|
|
167
224
|
/**
|
|
168
225
|
* Tear down all resources for a thread: leave the channel,
|
|
169
226
|
* disconnect the per-run socket, and remove the thread state.
|
|
227
|
+
*
|
|
228
|
+
* Idempotent — safe to call multiple times for the same threadId
|
|
229
|
+
* (e.g. from join error handlers, finalize, and Observable teardown).
|
|
170
230
|
*/
|
|
171
231
|
removeThread(threadId) {
|
|
172
232
|
const state = this.threads.get(threadId);
|
|
173
|
-
if (state)
|
|
233
|
+
if (!state) return;
|
|
234
|
+
this.threads.delete(threadId);
|
|
235
|
+
try {
|
|
174
236
|
state.channel.leave();
|
|
237
|
+
} catch {}
|
|
238
|
+
try {
|
|
175
239
|
state.socket.disconnect();
|
|
176
|
-
|
|
177
|
-
}
|
|
240
|
+
} catch {}
|
|
178
241
|
}
|
|
179
242
|
};
|
|
180
243
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligence.cjs","names":["AgentRunner","Socket","Observable","AG_UI_CHANNEL_EVENT","EventType","EMPTY"],"sources":["../../src/runner/intelligence.ts"],"sourcesContent":["import {\n AgentRunner,\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"./agent-runner\";\nimport { EMPTY, Observable, from } from \"rxjs\";\nimport { catchError, finalize } from \"rxjs/operators\";\nimport { AbstractAgent, BaseEvent, EventType } from \"@ag-ui/client\";\nimport {\n finalizeRunEvents,\n AG_UI_CHANNEL_EVENT,\n phoenixExponentialBackoff,\n} from \"@copilotkitnext/shared\";\nimport { Socket, Channel } from \"phoenix\";\n\nexport interface IntelligenceAgentRunnerOptions {\n /** Phoenix websocket URL, e.g. \"ws://localhost:4000/socket\" */\n url: string;\n /** Optional params sent on socket connect (e.g. auth token) */\n socketParams?: Record<string, string>;\n}\n\ninterface ThreadState {\n socket: Socket;\n channel: Channel;\n isRunning: boolean;\n stopRequested: boolean;\n agent: AbstractAgent | null;\n currentEvents: BaseEvent[];\n}\n\nexport class IntelligenceAgentRunner extends AgentRunner {\n private options: IntelligenceAgentRunnerOptions;\n private threads = new Map<string, ThreadState>();\n\n constructor(options: IntelligenceAgentRunnerOptions) {\n super();\n // Store config — sockets are created per-run, not eagerly.\n this.options = options;\n }\n\n /**\n * Create a new Phoenix socket with explicit exponential backoff.\n *\n * Each run/connect gets its own socket so that:\n * - A socket failure only affects a single thread, not all threads.\n * - Cleanup is simple: channel.leave() + socket.disconnect() tears\n * down everything for that run with no shared-state concerns.\n * - Each run gets its own independent retry budget.\n *\n * reconnectAfterMs — delay before Phoenix reconnects the WebSocket\n * after an unclean close. 100ms base, doubling up to a 10s cap.\n *\n * rejoinAfterMs — delay before Phoenix re-joins a channel that\n * entered the \"errored\" state. 1s base, doubling up to 30s cap.\n *\n * These are set explicitly because Phoenix's default schedule is a\n * fixed stepped array (not exponential), and any code that calls\n * socket.disconnect() in an onError handler will set\n * closeWasClean = true and reset the reconnect timer — permanently\n * killing retries.\n */\n private createSocket(): Socket {\n const socket = new Socket(this.options.url, {\n params: this.options.socketParams ?? {},\n reconnectAfterMs: phoenixExponentialBackoff(100, 10_000),\n rejoinAfterMs: phoenixExponentialBackoff(1_000, 30_000),\n });\n socket.connect();\n return socket;\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n const { threadId, agent, input } = request;\n\n const existing = this.threads.get(threadId);\n if (existing?.isRunning) {\n throw new Error(\"Thread already running\");\n }\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channel = socket.channel(`agent:${threadId}`, {\n runId: input.runId,\n });\n\n const state: ThreadState = {\n socket,\n channel,\n isRunning: true,\n stopRequested: false,\n agent,\n currentEvents: [],\n };\n this.threads.set(threadId, state);\n\n // Track consecutive socket errors for this run. Phoenix retries\n // automatically via reconnectAfterMs, but if the connection fails\n // repeatedly we abort the agent — otherwise runAgent() completes\n // normally, finalization events buffer silently on the dead\n // channel, and the client never receives them.\n //\n // Aborting the agent is the single trigger that cascades through\n // the existing error pipeline: runAgent() rejects → catchError\n // pushes RUN_ERROR → finalize calls finalizeRunEvents +\n // removeThread → channel.leave() + socket.disconnect().\n const MAX_CONSECUTIVE_ERRORS = 5;\n let consecutiveErrors = 0;\n\n socket.onError(() => {\n consecutiveErrors++;\n if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS && state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n // Otherwise: Phoenix retries automatically using the exponential\n // backoff schedule configured in createSocket().\n });\n\n socket.onOpen(() => {\n // A successful (re)connection resets the counter so transient\n // network blips don't accumulate across recoveries.\n consecutiveErrors = 0;\n });\n\n // Listen for custom \"stop\" events pushed by the client over the\n // channel. This must be registered before channel.join() so the\n // handler is in place by the time the server starts relaying messages.\n // The client sends the stop event before leaving the channel, so the\n // runner is guaranteed to receive it while still joined.\n channel.on(AG_UI_CHANNEL_EVENT, (payload: BaseEvent) => {\n if (\n payload.type === EventType.CUSTOM &&\n (payload as BaseEvent & { name?: string }).name === \"stop\"\n ) {\n this.stop({ threadId });\n }\n });\n\n channel\n .join()\n .receive(\"ok\", () => {\n this.executeAgentRun(request, state, threadId).subscribe({\n complete: () => observer.complete(),\n });\n })\n .receive(\"error\", (resp) => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: `Failed to join channel: ${JSON.stringify(resp)}`,\n code: \"CHANNEL_JOIN_ERROR\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n })\n .receive(\"timeout\", () => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: \"Timed out joining channel\",\n code: \"CHANNEL_JOIN_TIMEOUT\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n });\n\n return () => {\n this.removeThread(threadId);\n };\n });\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const { threadId } = request;\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channel = socket.channel(`agent:${threadId}`, {\n mode: \"connect\",\n });\n\n // Listen for AG-UI events on a single channel event name.\n channel.on(AG_UI_CHANNEL_EVENT, (payload: BaseEvent) => {\n observer.next(payload);\n\n if (\n payload.type === EventType.RUN_FINISHED ||\n payload.type === EventType.RUN_ERROR\n ) {\n observer.complete();\n }\n });\n\n const cleanup = () => {\n channel.leave();\n socket.disconnect();\n };\n\n channel\n .join()\n .receive(\"ok\", () => {\n // Ask the server to replay history via a CUSTOM event.\n channel.push(EventType.CUSTOM, {\n type: EventType.CUSTOM,\n name: \"connect\",\n value: { threadId },\n });\n })\n .receive(\"error\", (resp) => {\n observer.error(\n new Error(`Failed to join channel: ${JSON.stringify(resp)}`),\n );\n cleanup();\n })\n .receive(\"timeout\", () => {\n observer.error(new Error(\"Timed out joining channel\"));\n cleanup();\n });\n\n return () => {\n cleanup();\n };\n });\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n const state = this.threads.get(request.threadId);\n return Promise.resolve(state?.isRunning ?? false);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const state = this.threads.get(request.threadId);\n if (!state || !state.isRunning || state.stopRequested) {\n return Promise.resolve(false);\n }\n\n state.stopRequested = true;\n\n // Direct local abort — the runtime is the authority.\n if (state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n\n return Promise.resolve(true);\n }\n\n private executeAgentRun(\n request: AgentRunnerRunRequest,\n state: ThreadState,\n threadId: string,\n ): Observable<void> {\n const { currentEvents, channel } = state;\n\n return from(\n request.agent.runAgent(request.input, {\n onEvent: ({ event }: { event: BaseEvent }) => {\n currentEvents.push(event);\n\n // Push to Phoenix channel so frontend WS listeners receive it.\n channel.push(AG_UI_CHANNEL_EVENT, event);\n },\n }),\n ).pipe(\n catchError((error) => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: error instanceof Error ? error.message : String(error),\n } as BaseEvent;\n currentEvents.push(errorEvent);\n channel.push(AG_UI_CHANNEL_EVENT, errorEvent);\n return EMPTY;\n }),\n finalize(() => {\n const appended = finalizeRunEvents(currentEvents, {\n stopRequested: state.stopRequested,\n });\n for (const event of appended) {\n channel.push(AG_UI_CHANNEL_EVENT, event);\n }\n this.removeThread(threadId);\n }),\n );\n }\n\n /**\n * Tear down all resources for a thread: leave the channel,\n * disconnect the per-run socket, and remove the thread state.\n */\n private removeThread(threadId: string): void {\n const state = this.threads.get(threadId);\n if (state) {\n state.channel.leave();\n state.socket.disconnect();\n this.threads.delete(threadId);\n }\n }\n}\n"],"mappings":";;;;;;;;;AAiCA,IAAa,0BAAb,cAA6CA,iCAAY;CACvD,AAAQ;CACR,AAAQ,0BAAU,IAAI,KAA0B;CAEhD,YAAY,SAAyC;AACnD,SAAO;AAEP,OAAK,UAAU;;;;;;;;;;;;;;;;;;;;;;;CAwBjB,AAAQ,eAAuB;EAC7B,MAAM,SAAS,IAAIC,eAAO,KAAK,QAAQ,KAAK;GAC1C,QAAQ,KAAK,QAAQ,gBAAgB,EAAE;GACvC,wEAA4C,KAAK,IAAO;GACxD,qEAAyC,KAAO,IAAO;GACxD,CAAC;AACF,SAAO,SAAS;AAChB,SAAO;;CAGT,IAAI,SAAuD;EACzD,MAAM,EAAE,UAAU,OAAO,UAAU;AAGnC,MADiB,KAAK,QAAQ,IAAI,SAAS,EAC7B,UACZ,OAAM,IAAI,MAAM,yBAAyB;AAG3C,SAAO,IAAIC,iBAAY,aAAa;GAClC,MAAM,SAAS,KAAK,cAAc;GAElC,MAAM,UAAU,OAAO,QAAQ,SAAS,YAAY,EAClD,OAAO,MAAM,OACd,CAAC;GAEF,MAAM,QAAqB;IACzB;IACA;IACA,WAAW;IACX,eAAe;IACf;IACA,eAAe,EAAE;IAClB;AACD,QAAK,QAAQ,IAAI,UAAU,MAAM;GAYjC,MAAM,yBAAyB;GAC/B,IAAI,oBAAoB;AAExB,UAAO,cAAc;AACnB;AACA,QAAI,qBAAqB,0BAA0B,MAAM,MACvD,KAAI;AACF,WAAM,MAAM,UAAU;YAChB;KAMV;AAEF,UAAO,aAAa;AAGlB,wBAAoB;KACpB;AAOF,WAAQ,GAAGC,6CAAsB,YAAuB;AACtD,QACE,QAAQ,SAASC,wBAAU,UAC1B,QAA0C,SAAS,OAEpD,MAAK,KAAK,EAAE,UAAU,CAAC;KAEzB;AAEF,WACG,MAAM,CACN,QAAQ,YAAY;AACnB,SAAK,gBAAgB,SAAS,OAAO,SAAS,CAAC,UAAU,EACvD,gBAAgB,SAAS,UAAU,EACpC,CAAC;KACF,CACD,QAAQ,UAAU,SAAS;IAC1B,MAAM,aAAa;KACjB,MAAMA,wBAAU;KAChB,SAAS,2BAA2B,KAAK,UAAU,KAAK;KACxD,MAAM;KACP;AACD,aAAS,KAAK,WAAW;AACzB,UAAM,cAAc,KAAK,WAAW;AACpC,SAAK,aAAa,SAAS;AAC3B,aAAS,UAAU;KACnB,CACD,QAAQ,iBAAiB;IACxB,MAAM,aAAa;KACjB,MAAMA,wBAAU;KAChB,SAAS;KACT,MAAM;KACP;AACD,aAAS,KAAK,WAAW;AACzB,UAAM,cAAc,KAAK,WAAW;AACpC,SAAK,aAAa,SAAS;AAC3B,aAAS,UAAU;KACnB;AAEJ,gBAAa;AACX,SAAK,aAAa,SAAS;;IAE7B;;CAGJ,QAAQ,SAA2D;EACjE,MAAM,EAAE,aAAa;AAErB,SAAO,IAAIF,iBAAY,aAAa;GAClC,MAAM,SAAS,KAAK,cAAc;GAElC,MAAM,UAAU,OAAO,QAAQ,SAAS,YAAY,EAClD,MAAM,WACP,CAAC;AAGF,WAAQ,GAAGC,6CAAsB,YAAuB;AACtD,aAAS,KAAK,QAAQ;AAEtB,QACE,QAAQ,SAASC,wBAAU,gBAC3B,QAAQ,SAASA,wBAAU,UAE3B,UAAS,UAAU;KAErB;GAEF,MAAM,gBAAgB;AACpB,YAAQ,OAAO;AACf,WAAO,YAAY;;AAGrB,WACG,MAAM,CACN,QAAQ,YAAY;AAEnB,YAAQ,KAAKA,wBAAU,QAAQ;KAC7B,MAAMA,wBAAU;KAChB,MAAM;KACN,OAAO,EAAE,UAAU;KACpB,CAAC;KACF,CACD,QAAQ,UAAU,SAAS;AAC1B,aAAS,sBACP,IAAI,MAAM,2BAA2B,KAAK,UAAU,KAAK,GAAG,CAC7D;AACD,aAAS;KACT,CACD,QAAQ,iBAAiB;AACxB,aAAS,sBAAM,IAAI,MAAM,4BAA4B,CAAC;AACtD,aAAS;KACT;AAEJ,gBAAa;AACX,aAAS;;IAEX;;CAGJ,UAAU,SAAwD;EAChE,MAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ,SAAS;AAChD,SAAO,QAAQ,QAAQ,OAAO,aAAa,MAAM;;CAGnD,KAAK,SAA+D;EAClE,MAAM,QAAQ,KAAK,QAAQ,IAAI,QAAQ,SAAS;AAChD,MAAI,CAAC,SAAS,CAAC,MAAM,aAAa,MAAM,cACtC,QAAO,QAAQ,QAAQ,MAAM;AAG/B,QAAM,gBAAgB;AAGtB,MAAI,MAAM,MACR,KAAI;AACF,SAAM,MAAM,UAAU;UAChB;AAKV,SAAO,QAAQ,QAAQ,KAAK;;CAG9B,AAAQ,gBACN,SACA,OACA,UACkB;EAClB,MAAM,EAAE,eAAe,YAAY;AAEnC,wBACE,QAAQ,MAAM,SAAS,QAAQ,OAAO,EACpC,UAAU,EAAE,YAAkC;AAC5C,iBAAc,KAAK,MAAM;AAGzB,WAAQ,KAAKD,4CAAqB,MAAM;KAE3C,CAAC,CACH,CAAC,qCACY,UAAU;GACpB,MAAM,aAAa;IACjB,MAAMC,wBAAU;IAChB,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAChE;AACD,iBAAc,KAAK,WAAW;AAC9B,WAAQ,KAAKD,4CAAqB,WAAW;AAC7C,UAAOE;IACP,qCACa;GACb,MAAM,yDAA6B,eAAe,EAChD,eAAe,MAAM,eACtB,CAAC;AACF,QAAK,MAAM,SAAS,SAClB,SAAQ,KAAKF,4CAAqB,MAAM;AAE1C,QAAK,aAAa,SAAS;IAC3B,CACH;;;;;;CAOH,AAAQ,aAAa,UAAwB;EAC3C,MAAM,QAAQ,KAAK,QAAQ,IAAI,SAAS;AACxC,MAAI,OAAO;AACT,SAAM,QAAQ,OAAO;AACrB,SAAM,OAAO,YAAY;AACzB,QAAK,QAAQ,OAAO,SAAS"}
|
|
1
|
+
{"version":3,"file":"intelligence.cjs","names":["AgentRunner","Socket","Observable","AG_UI_CHANNEL_EVENT","EventType","EMPTY"],"sources":["../../src/runner/intelligence.ts"],"sourcesContent":["import {\n AgentRunner,\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"./agent-runner\";\nimport { EMPTY, Observable, from } from \"rxjs\";\nimport { catchError, finalize } from \"rxjs/operators\";\nimport {\n AbstractAgent,\n BaseEvent,\n EventType,\n RunStartedEvent,\n} from \"@ag-ui/client\";\nimport {\n finalizeRunEvents,\n AG_UI_CHANNEL_EVENT,\n phoenixExponentialBackoff,\n} from \"@copilotkitnext/shared\";\nimport { Socket, Channel } from \"phoenix\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface IntelligenceAgentRunnerOptions {\n /** Phoenix runner websocket URL, e.g. \"ws://localhost:4000/runner\" */\n url: string;\n /** Optional Phoenix socket auth token used during websocket connect. */\n authToken?: string;\n}\n\ninterface ThreadState {\n socket: Socket;\n channel: Channel;\n isRunning: boolean;\n stopRequested: boolean;\n agent: AbstractAgent | null;\n currentEvents: BaseEvent[];\n nextEventSeq: number;\n hasRunStarted: boolean;\n}\n\nexport class IntelligenceAgentRunner extends AgentRunner {\n private options: IntelligenceAgentRunnerOptions;\n private threads = new Map<string, ThreadState>();\n\n constructor(options: IntelligenceAgentRunnerOptions) {\n super();\n // Store config — sockets are created per-run, not eagerly.\n this.options = options;\n }\n\n /**\n * Create a new Phoenix socket with explicit exponential backoff.\n *\n * Each run/connect gets its own socket so that:\n * - A socket failure only affects a single thread, not all threads.\n * - Cleanup is simple: channel.leave() + socket.disconnect() tears\n * down everything for that run with no shared-state concerns.\n * - Each run gets its own independent retry budget.\n *\n * reconnectAfterMs — delay before Phoenix reconnects the WebSocket\n * after an unclean close. 100ms base, doubling up to a 10s cap.\n *\n * rejoinAfterMs — delay before Phoenix re-joins a channel that\n * entered the \"errored\" state. 1s base, doubling up to 30s cap.\n *\n * These are set explicitly because Phoenix's default schedule is a\n * fixed stepped array (not exponential), and any code that calls\n * socket.disconnect() in an onError handler will set\n * closeWasClean = true and reset the reconnect timer — permanently\n * killing retries.\n */\n private createSocket(): Socket {\n const socket = new Socket(this.options.url, {\n ...(this.options.authToken ? { authToken: this.options.authToken } : {}),\n reconnectAfterMs: phoenixExponentialBackoff(100, 10_000),\n rejoinAfterMs: phoenixExponentialBackoff(1_000, 30_000),\n });\n socket.connect();\n return socket;\n }\n\n private createRunnerEventPayload(\n event: BaseEvent,\n request: AgentRunnerRunRequest,\n state: ThreadState,\n ): Record<string, unknown> {\n const canonicalEvent = this.stampRunnerMetadata(event, state);\n const payload = {\n ...(canonicalEvent as Record<string, unknown>),\n };\n\n payload.thread_id ??= request.threadId;\n\n const runId = payload.runId ?? payload.run_id ?? request.input.runId;\n\n if (runId) {\n payload.run_id = runId;\n }\n\n return payload;\n }\n\n private stampRunnerMetadata(event: BaseEvent, state: ThreadState): BaseEvent {\n const eventRecord = event as BaseEvent & {\n metadata?: Record<string, unknown>;\n };\n\n const existingMetadata = eventRecord.metadata ?? {};\n const hasEventId = typeof existingMetadata.cpki_event_id === \"string\";\n const hasEventSeq = typeof existingMetadata.cpki_event_seq === \"number\";\n\n if (hasEventId && hasEventSeq) {\n const eventSeq = existingMetadata.cpki_event_seq as number;\n state.nextEventSeq = Math.max(state.nextEventSeq, eventSeq + 1);\n return eventRecord;\n }\n\n const eventSeq = state.nextEventSeq++;\n\n return {\n ...eventRecord,\n metadata: {\n ...existingMetadata,\n cpki_event_id:\n typeof existingMetadata.cpki_event_id === \"string\"\n ? existingMetadata.cpki_event_id\n : randomUUID(),\n cpki_event_seq: eventSeq,\n },\n };\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n const { threadId, agent, input, joinCode } = request;\n\n const existing = this.threads.get(threadId);\n if (existing?.isRunning) {\n throw new Error(\"Thread already running\");\n }\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channelTopic = joinCode ?? threadId;\n const channel = socket.channel(`ingestion:${channelTopic}`, {\n runId: input.runId,\n });\n\n const state: ThreadState = {\n socket,\n channel,\n isRunning: true,\n stopRequested: false,\n agent,\n currentEvents: [],\n nextEventSeq: 1,\n hasRunStarted: false,\n };\n this.threads.set(threadId, state);\n\n // Track consecutive socket errors for this run. Phoenix retries\n // automatically via reconnectAfterMs, but if the connection fails\n // repeatedly we abort the agent — otherwise runAgent() completes\n // normally, finalization events buffer silently on the dead\n // channel, and the client never receives them.\n //\n // Aborting the agent is the single trigger that cascades through\n // the existing error pipeline: runAgent() rejects → catchError\n // pushes RUN_ERROR → finalize calls finalizeRunEvents +\n // removeThread → channel.leave() + socket.disconnect().\n const MAX_CONSECUTIVE_ERRORS = 5;\n let consecutiveErrors = 0;\n\n socket.onError(() => {\n consecutiveErrors++;\n if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS && state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n // Otherwise: Phoenix retries automatically using the exponential\n // backoff schedule configured in createSocket().\n });\n\n socket.onOpen(() => {\n // A successful (re)connection resets the counter so transient\n // network blips don't accumulate across recoveries.\n consecutiveErrors = 0;\n });\n\n // Listen for custom \"stop\" events pushed by the client over the\n // channel. This must be registered before channel.join() so the\n // handler is in place by the time the server starts relaying messages.\n // The client sends the stop event before leaving the channel, so the\n // runner is guaranteed to receive it while still joined.\n channel.on(AG_UI_CHANNEL_EVENT, (payload: BaseEvent) => {\n if (\n payload.type === EventType.CUSTOM &&\n (payload as BaseEvent & { name?: string }).name === \"stop\"\n ) {\n this.stop({ threadId });\n }\n });\n\n channel\n .join()\n .receive(\"ok\", () => {\n this.executeAgentRun(request, state, threadId).subscribe({\n complete: () => observer.complete(),\n });\n })\n .receive(\"error\", (resp) => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: `Failed to join channel: ${JSON.stringify(resp)}`,\n code: \"CHANNEL_JOIN_ERROR\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n })\n .receive(\"timeout\", () => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: \"Timed out joining channel\",\n code: \"CHANNEL_JOIN_TIMEOUT\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n });\n\n return () => {\n this.removeThread(threadId);\n };\n });\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const { threadId } = request;\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channel = socket.channel(`thread:${threadId}`);\n\n channel.on(\"ag_ui_event\", (payload: BaseEvent) => {\n observer.next(payload);\n\n if (\n payload.type === EventType.RUN_FINISHED ||\n payload.type === EventType.RUN_ERROR\n ) {\n observer.complete();\n }\n });\n\n const cleanup = () => {\n channel.leave();\n socket.disconnect();\n };\n\n channel\n .join()\n .receive(\"ok\", () => undefined)\n .receive(\"error\", (resp) => {\n observer.error(\n new Error(`Failed to join channel: ${JSON.stringify(resp)}`),\n );\n cleanup();\n })\n .receive(\"timeout\", () => {\n observer.error(new Error(\"Timed out joining channel\"));\n cleanup();\n });\n\n return () => {\n cleanup();\n };\n });\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n const state = this.threads.get(request.threadId);\n return Promise.resolve(state?.isRunning ?? false);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const state = this.threads.get(request.threadId);\n if (!state || !state.isRunning || state.stopRequested) {\n return Promise.resolve(false);\n }\n\n state.stopRequested = true;\n\n // Direct local abort — the runtime is the authority.\n if (state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n\n return Promise.resolve(true);\n }\n\n private executeAgentRun(\n request: AgentRunnerRunRequest,\n state: ThreadState,\n threadId: string,\n ): Observable<void> {\n const { currentEvents, channel } = state;\n const pushCanonicalEvent = (event: BaseEvent): void => {\n const canonicalEvent = this.stampRunnerMetadata(event, state);\n currentEvents.push(canonicalEvent);\n\n if (canonicalEvent.type === EventType.RUN_STARTED) {\n state.hasRunStarted = true;\n }\n\n channel.push(\n \"event\",\n this.createRunnerEventPayload(canonicalEvent, request, state),\n );\n };\n\n const getPersistedInputMessages = () =>\n request.persistedInputMessages ?? request.input.messages;\n\n const buildRunStartedEvent = (\n source?: RunStartedEvent,\n ): RunStartedEvent => {\n const baseInput = source?.input ?? request.input;\n const persistedInputMessages = getPersistedInputMessages();\n\n return {\n ...(source ?? {\n type: EventType.RUN_STARTED,\n threadId: request.threadId,\n runId: request.input.runId,\n }),\n input: {\n ...baseInput,\n ...(persistedInputMessages !== undefined\n ? { messages: persistedInputMessages }\n : {}),\n },\n } as RunStartedEvent;\n };\n\n const ensureRunStarted = (): void => {\n if (!state.hasRunStarted) {\n state.hasRunStarted = true;\n pushCanonicalEvent(buildRunStartedEvent());\n }\n };\n\n return from(\n request.agent.runAgent(request.input, {\n onEvent: ({ event }: { event: BaseEvent }) => {\n if (event.type === EventType.RUN_STARTED) {\n pushCanonicalEvent(buildRunStartedEvent(event as RunStartedEvent));\n return;\n }\n\n ensureRunStarted();\n pushCanonicalEvent(event);\n },\n }),\n ).pipe(\n catchError((error) => {\n ensureRunStarted();\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: error instanceof Error ? error.message : String(error),\n } as BaseEvent;\n pushCanonicalEvent(errorEvent);\n return EMPTY;\n }),\n finalize(() => {\n ensureRunStarted();\n const appended = finalizeRunEvents(currentEvents, {\n stopRequested: state.stopRequested,\n });\n for (const event of appended) {\n channel.push(\n \"event\",\n this.createRunnerEventPayload(event, request, state),\n );\n }\n this.removeThread(threadId);\n }),\n );\n }\n\n /**\n * Tear down all resources for a thread: leave the channel,\n * disconnect the per-run socket, and remove the thread state.\n *\n * Idempotent — safe to call multiple times for the same threadId\n * (e.g. from join error handlers, finalize, and Observable teardown).\n */\n private removeThread(threadId: string): void {\n const state = this.threads.get(threadId);\n if (!state) {\n return;\n }\n\n // Delete first so concurrent calls see the entry as already removed.\n this.threads.delete(threadId);\n\n try {\n state.channel.leave();\n } catch {\n // Channel may already be closed/left.\n }\n try {\n state.socket.disconnect();\n } catch {\n // Socket may already be disconnected.\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -4,10 +4,10 @@ import { Observable } from "rxjs";
|
|
|
4
4
|
|
|
5
5
|
//#region src/runner/intelligence.d.ts
|
|
6
6
|
interface IntelligenceAgentRunnerOptions {
|
|
7
|
-
/** Phoenix websocket URL, e.g. "ws://localhost:4000/
|
|
7
|
+
/** Phoenix runner websocket URL, e.g. "ws://localhost:4000/runner" */
|
|
8
8
|
url: string;
|
|
9
|
-
/** Optional
|
|
10
|
-
|
|
9
|
+
/** Optional Phoenix socket auth token used during websocket connect. */
|
|
10
|
+
authToken?: string;
|
|
11
11
|
}
|
|
12
12
|
declare class IntelligenceAgentRunner extends AgentRunner {
|
|
13
13
|
private options;
|
|
@@ -35,6 +35,8 @@ declare class IntelligenceAgentRunner extends AgentRunner {
|
|
|
35
35
|
* killing retries.
|
|
36
36
|
*/
|
|
37
37
|
private createSocket;
|
|
38
|
+
private createRunnerEventPayload;
|
|
39
|
+
private stampRunnerMetadata;
|
|
38
40
|
run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
|
|
39
41
|
connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
|
|
40
42
|
isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
|
|
@@ -43,6 +45,9 @@ declare class IntelligenceAgentRunner extends AgentRunner {
|
|
|
43
45
|
/**
|
|
44
46
|
* Tear down all resources for a thread: leave the channel,
|
|
45
47
|
* disconnect the per-run socket, and remove the thread state.
|
|
48
|
+
*
|
|
49
|
+
* Idempotent — safe to call multiple times for the same threadId
|
|
50
|
+
* (e.g. from join error handlers, finalize, and Observable teardown).
|
|
46
51
|
*/
|
|
47
52
|
private removeThread;
|
|
48
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligence.d.cts","names":[],"sources":["../../src/runner/intelligence.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"intelligence.d.cts","names":[],"sources":["../../src/runner/intelligence.ts"],"mappings":";;;;;UAuBiB,8BAAA;;EAEf,GAAA;EAF6C;EAI7C,SAAA;AAAA;AAAA,cAcW,uBAAA,SAAgC,WAAA;EAAA,QACnC,OAAA;EAAA,QACA,OAAA;cAEI,OAAA,EAAS,8BAAA;EAAA;;;;;;;;;;;;;;;;;;;;;EAAA,QA2Bb,YAAA;EAAA,QAUA,wBAAA;EAAA,QAqBA,mBAAA;EA8BR,GAAA,CAAI,OAAA,EAAS,qBAAA,GAAwB,UAAA,CAAW,SAAA;EA8GhD,OAAA,CAAQ,OAAA,EAAS,yBAAA,GAA4B,UAAA,CAAW,SAAA;EA4CxD,SAAA,CAAU,OAAA,EAAS,2BAAA,GAA8B,OAAA;EAKjD,IAAA,CAAK,OAAA,EAAS,sBAAA,GAAyB,OAAA;EAAA,QAoB/B,eAAA;EArES;;;;;;;EAAA,QAqKT,YAAA;AAAA"}
|
|
@@ -4,10 +4,10 @@ import { BaseEvent } from "@ag-ui/client";
|
|
|
4
4
|
|
|
5
5
|
//#region src/runner/intelligence.d.ts
|
|
6
6
|
interface IntelligenceAgentRunnerOptions {
|
|
7
|
-
/** Phoenix websocket URL, e.g. "ws://localhost:4000/
|
|
7
|
+
/** Phoenix runner websocket URL, e.g. "ws://localhost:4000/runner" */
|
|
8
8
|
url: string;
|
|
9
|
-
/** Optional
|
|
10
|
-
|
|
9
|
+
/** Optional Phoenix socket auth token used during websocket connect. */
|
|
10
|
+
authToken?: string;
|
|
11
11
|
}
|
|
12
12
|
declare class IntelligenceAgentRunner extends AgentRunner {
|
|
13
13
|
private options;
|
|
@@ -35,6 +35,8 @@ declare class IntelligenceAgentRunner extends AgentRunner {
|
|
|
35
35
|
* killing retries.
|
|
36
36
|
*/
|
|
37
37
|
private createSocket;
|
|
38
|
+
private createRunnerEventPayload;
|
|
39
|
+
private stampRunnerMetadata;
|
|
38
40
|
run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
|
|
39
41
|
connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
|
|
40
42
|
isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
|
|
@@ -43,6 +45,9 @@ declare class IntelligenceAgentRunner extends AgentRunner {
|
|
|
43
45
|
/**
|
|
44
46
|
* Tear down all resources for a thread: leave the channel,
|
|
45
47
|
* disconnect the per-run socket, and remove the thread state.
|
|
48
|
+
*
|
|
49
|
+
* Idempotent — safe to call multiple times for the same threadId
|
|
50
|
+
* (e.g. from join error handlers, finalize, and Observable teardown).
|
|
46
51
|
*/
|
|
47
52
|
private removeThread;
|
|
48
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligence.d.mts","names":[],"sources":["../../src/runner/intelligence.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"intelligence.d.mts","names":[],"sources":["../../src/runner/intelligence.ts"],"mappings":";;;;;UAuBiB,8BAAA;;EAEf,GAAA;EAF6C;EAI7C,SAAA;AAAA;AAAA,cAcW,uBAAA,SAAgC,WAAA;EAAA,QACnC,OAAA;EAAA,QACA,OAAA;cAEI,OAAA,EAAS,8BAAA;EAAA;;;;;;;;;;;;;;;;;;;;;EAAA,QA2Bb,YAAA;EAAA,QAUA,wBAAA;EAAA,QAqBA,mBAAA;EA8BR,GAAA,CAAI,OAAA,EAAS,qBAAA,GAAwB,UAAA,CAAW,SAAA;EA8GhD,OAAA,CAAQ,OAAA,EAAS,yBAAA,GAA4B,UAAA,CAAW,SAAA;EA4CxD,SAAA,CAAU,OAAA,EAAS,2BAAA,GAA8B,OAAA;EAKjD,IAAA,CAAK,OAAA,EAAS,sBAAA,GAAyB,OAAA;EAAA,QAoB/B,eAAA;EArES;;;;;;;EAAA,QAqKT,YAAA;AAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { AgentRunner } from "./agent-runner.mjs";
|
|
2
|
+
import { AG_UI_CHANNEL_EVENT, finalizeRunEvents, phoenixExponentialBackoff } from "@copilotkitnext/shared";
|
|
2
3
|
import { EMPTY, Observable, from } from "rxjs";
|
|
3
4
|
import { EventType } from "@ag-ui/client";
|
|
4
|
-
import { AG_UI_CHANNEL_EVENT, finalizeRunEvents, phoenixExponentialBackoff } from "@copilotkitnext/shared";
|
|
5
5
|
import { catchError, finalize } from "rxjs/operators";
|
|
6
6
|
import { Socket } from "phoenix";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
7
8
|
|
|
8
9
|
//#region src/runner/intelligence.ts
|
|
9
10
|
var IntelligenceAgentRunner = class extends AgentRunner {
|
|
@@ -36,26 +37,56 @@ var IntelligenceAgentRunner = class extends AgentRunner {
|
|
|
36
37
|
*/
|
|
37
38
|
createSocket() {
|
|
38
39
|
const socket = new Socket(this.options.url, {
|
|
39
|
-
|
|
40
|
+
...this.options.authToken ? { authToken: this.options.authToken } : {},
|
|
40
41
|
reconnectAfterMs: phoenixExponentialBackoff(100, 1e4),
|
|
41
42
|
rejoinAfterMs: phoenixExponentialBackoff(1e3, 3e4)
|
|
42
43
|
});
|
|
43
44
|
socket.connect();
|
|
44
45
|
return socket;
|
|
45
46
|
}
|
|
47
|
+
createRunnerEventPayload(event, request, state) {
|
|
48
|
+
const payload = { ...this.stampRunnerMetadata(event, state) };
|
|
49
|
+
payload.thread_id ??= request.threadId;
|
|
50
|
+
const runId = payload.runId ?? payload.run_id ?? request.input.runId;
|
|
51
|
+
if (runId) payload.run_id = runId;
|
|
52
|
+
return payload;
|
|
53
|
+
}
|
|
54
|
+
stampRunnerMetadata(event, state) {
|
|
55
|
+
const eventRecord = event;
|
|
56
|
+
const existingMetadata = eventRecord.metadata ?? {};
|
|
57
|
+
const hasEventId = typeof existingMetadata.cpki_event_id === "string";
|
|
58
|
+
const hasEventSeq = typeof existingMetadata.cpki_event_seq === "number";
|
|
59
|
+
if (hasEventId && hasEventSeq) {
|
|
60
|
+
const eventSeq = existingMetadata.cpki_event_seq;
|
|
61
|
+
state.nextEventSeq = Math.max(state.nextEventSeq, eventSeq + 1);
|
|
62
|
+
return eventRecord;
|
|
63
|
+
}
|
|
64
|
+
const eventSeq = state.nextEventSeq++;
|
|
65
|
+
return {
|
|
66
|
+
...eventRecord,
|
|
67
|
+
metadata: {
|
|
68
|
+
...existingMetadata,
|
|
69
|
+
cpki_event_id: typeof existingMetadata.cpki_event_id === "string" ? existingMetadata.cpki_event_id : randomUUID(),
|
|
70
|
+
cpki_event_seq: eventSeq
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
46
74
|
run(request) {
|
|
47
|
-
const { threadId, agent, input } = request;
|
|
75
|
+
const { threadId, agent, input, joinCode } = request;
|
|
48
76
|
if (this.threads.get(threadId)?.isRunning) throw new Error("Thread already running");
|
|
49
77
|
return new Observable((observer) => {
|
|
50
78
|
const socket = this.createSocket();
|
|
51
|
-
const
|
|
79
|
+
const channelTopic = joinCode ?? threadId;
|
|
80
|
+
const channel = socket.channel(`ingestion:${channelTopic}`, { runId: input.runId });
|
|
52
81
|
const state = {
|
|
53
82
|
socket,
|
|
54
83
|
channel,
|
|
55
84
|
isRunning: true,
|
|
56
85
|
stopRequested: false,
|
|
57
86
|
agent,
|
|
58
|
-
currentEvents: []
|
|
87
|
+
currentEvents: [],
|
|
88
|
+
nextEventSeq: 1,
|
|
89
|
+
hasRunStarted: false
|
|
59
90
|
};
|
|
60
91
|
this.threads.set(threadId, state);
|
|
61
92
|
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
@@ -104,8 +135,8 @@ var IntelligenceAgentRunner = class extends AgentRunner {
|
|
|
104
135
|
const { threadId } = request;
|
|
105
136
|
return new Observable((observer) => {
|
|
106
137
|
const socket = this.createSocket();
|
|
107
|
-
const channel = socket.channel(`
|
|
108
|
-
channel.on(
|
|
138
|
+
const channel = socket.channel(`thread:${threadId}`);
|
|
139
|
+
channel.on("ag_ui_event", (payload) => {
|
|
109
140
|
observer.next(payload);
|
|
110
141
|
if (payload.type === EventType.RUN_FINISHED || payload.type === EventType.RUN_ERROR) observer.complete();
|
|
111
142
|
});
|
|
@@ -113,13 +144,7 @@ var IntelligenceAgentRunner = class extends AgentRunner {
|
|
|
113
144
|
channel.leave();
|
|
114
145
|
socket.disconnect();
|
|
115
146
|
};
|
|
116
|
-
channel.join().receive("ok", () => {
|
|
117
|
-
channel.push(EventType.CUSTOM, {
|
|
118
|
-
type: EventType.CUSTOM,
|
|
119
|
-
name: "connect",
|
|
120
|
-
value: { threadId }
|
|
121
|
-
});
|
|
122
|
-
}).receive("error", (resp) => {
|
|
147
|
+
channel.join().receive("ok", () => void 0).receive("error", (resp) => {
|
|
123
148
|
observer.error(/* @__PURE__ */ new Error(`Failed to join channel: ${JSON.stringify(resp)}`));
|
|
124
149
|
cleanup();
|
|
125
150
|
}).receive("timeout", () => {
|
|
@@ -146,34 +171,72 @@ var IntelligenceAgentRunner = class extends AgentRunner {
|
|
|
146
171
|
}
|
|
147
172
|
executeAgentRun(request, state, threadId) {
|
|
148
173
|
const { currentEvents, channel } = state;
|
|
174
|
+
const pushCanonicalEvent = (event) => {
|
|
175
|
+
const canonicalEvent = this.stampRunnerMetadata(event, state);
|
|
176
|
+
currentEvents.push(canonicalEvent);
|
|
177
|
+
if (canonicalEvent.type === EventType.RUN_STARTED) state.hasRunStarted = true;
|
|
178
|
+
channel.push("event", this.createRunnerEventPayload(canonicalEvent, request, state));
|
|
179
|
+
};
|
|
180
|
+
const getPersistedInputMessages = () => request.persistedInputMessages ?? request.input.messages;
|
|
181
|
+
const buildRunStartedEvent = (source) => {
|
|
182
|
+
const baseInput = source?.input ?? request.input;
|
|
183
|
+
const persistedInputMessages = getPersistedInputMessages();
|
|
184
|
+
return {
|
|
185
|
+
...source ?? {
|
|
186
|
+
type: EventType.RUN_STARTED,
|
|
187
|
+
threadId: request.threadId,
|
|
188
|
+
runId: request.input.runId
|
|
189
|
+
},
|
|
190
|
+
input: {
|
|
191
|
+
...baseInput,
|
|
192
|
+
...persistedInputMessages !== void 0 ? { messages: persistedInputMessages } : {}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
const ensureRunStarted = () => {
|
|
197
|
+
if (!state.hasRunStarted) {
|
|
198
|
+
state.hasRunStarted = true;
|
|
199
|
+
pushCanonicalEvent(buildRunStartedEvent());
|
|
200
|
+
}
|
|
201
|
+
};
|
|
149
202
|
return from(request.agent.runAgent(request.input, { onEvent: ({ event }) => {
|
|
150
|
-
|
|
151
|
-
|
|
203
|
+
if (event.type === EventType.RUN_STARTED) {
|
|
204
|
+
pushCanonicalEvent(buildRunStartedEvent(event));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
ensureRunStarted();
|
|
208
|
+
pushCanonicalEvent(event);
|
|
152
209
|
} })).pipe(catchError((error) => {
|
|
153
|
-
|
|
210
|
+
ensureRunStarted();
|
|
211
|
+
pushCanonicalEvent({
|
|
154
212
|
type: EventType.RUN_ERROR,
|
|
155
213
|
message: error instanceof Error ? error.message : String(error)
|
|
156
|
-
};
|
|
157
|
-
currentEvents.push(errorEvent);
|
|
158
|
-
channel.push(AG_UI_CHANNEL_EVENT, errorEvent);
|
|
214
|
+
});
|
|
159
215
|
return EMPTY;
|
|
160
216
|
}), finalize(() => {
|
|
217
|
+
ensureRunStarted();
|
|
161
218
|
const appended = finalizeRunEvents(currentEvents, { stopRequested: state.stopRequested });
|
|
162
|
-
for (const event of appended) channel.push(
|
|
219
|
+
for (const event of appended) channel.push("event", this.createRunnerEventPayload(event, request, state));
|
|
163
220
|
this.removeThread(threadId);
|
|
164
221
|
}));
|
|
165
222
|
}
|
|
166
223
|
/**
|
|
167
224
|
* Tear down all resources for a thread: leave the channel,
|
|
168
225
|
* disconnect the per-run socket, and remove the thread state.
|
|
226
|
+
*
|
|
227
|
+
* Idempotent — safe to call multiple times for the same threadId
|
|
228
|
+
* (e.g. from join error handlers, finalize, and Observable teardown).
|
|
169
229
|
*/
|
|
170
230
|
removeThread(threadId) {
|
|
171
231
|
const state = this.threads.get(threadId);
|
|
172
|
-
if (state)
|
|
232
|
+
if (!state) return;
|
|
233
|
+
this.threads.delete(threadId);
|
|
234
|
+
try {
|
|
173
235
|
state.channel.leave();
|
|
236
|
+
} catch {}
|
|
237
|
+
try {
|
|
174
238
|
state.socket.disconnect();
|
|
175
|
-
|
|
176
|
-
}
|
|
239
|
+
} catch {}
|
|
177
240
|
}
|
|
178
241
|
};
|
|
179
242
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intelligence.mjs","names":[],"sources":["../../src/runner/intelligence.ts"],"sourcesContent":["import {\n AgentRunner,\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"./agent-runner\";\nimport { EMPTY, Observable, from } from \"rxjs\";\nimport { catchError, finalize } from \"rxjs/operators\";\nimport { AbstractAgent, BaseEvent, EventType } from \"@ag-ui/client\";\nimport {\n finalizeRunEvents,\n AG_UI_CHANNEL_EVENT,\n phoenixExponentialBackoff,\n} from \"@copilotkitnext/shared\";\nimport { Socket, Channel } from \"phoenix\";\n\nexport interface IntelligenceAgentRunnerOptions {\n /** Phoenix websocket URL, e.g. \"ws://localhost:4000/socket\" */\n url: string;\n /** Optional params sent on socket connect (e.g. auth token) */\n socketParams?: Record<string, string>;\n}\n\ninterface ThreadState {\n socket: Socket;\n channel: Channel;\n isRunning: boolean;\n stopRequested: boolean;\n agent: AbstractAgent | null;\n currentEvents: BaseEvent[];\n}\n\nexport class IntelligenceAgentRunner extends AgentRunner {\n private options: IntelligenceAgentRunnerOptions;\n private threads = new Map<string, ThreadState>();\n\n constructor(options: IntelligenceAgentRunnerOptions) {\n super();\n // Store config — sockets are created per-run, not eagerly.\n this.options = options;\n }\n\n /**\n * Create a new Phoenix socket with explicit exponential backoff.\n *\n * Each run/connect gets its own socket so that:\n * - A socket failure only affects a single thread, not all threads.\n * - Cleanup is simple: channel.leave() + socket.disconnect() tears\n * down everything for that run with no shared-state concerns.\n * - Each run gets its own independent retry budget.\n *\n * reconnectAfterMs — delay before Phoenix reconnects the WebSocket\n * after an unclean close. 100ms base, doubling up to a 10s cap.\n *\n * rejoinAfterMs — delay before Phoenix re-joins a channel that\n * entered the \"errored\" state. 1s base, doubling up to 30s cap.\n *\n * These are set explicitly because Phoenix's default schedule is a\n * fixed stepped array (not exponential), and any code that calls\n * socket.disconnect() in an onError handler will set\n * closeWasClean = true and reset the reconnect timer — permanently\n * killing retries.\n */\n private createSocket(): Socket {\n const socket = new Socket(this.options.url, {\n params: this.options.socketParams ?? {},\n reconnectAfterMs: phoenixExponentialBackoff(100, 10_000),\n rejoinAfterMs: phoenixExponentialBackoff(1_000, 30_000),\n });\n socket.connect();\n return socket;\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n const { threadId, agent, input } = request;\n\n const existing = this.threads.get(threadId);\n if (existing?.isRunning) {\n throw new Error(\"Thread already running\");\n }\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channel = socket.channel(`agent:${threadId}`, {\n runId: input.runId,\n });\n\n const state: ThreadState = {\n socket,\n channel,\n isRunning: true,\n stopRequested: false,\n agent,\n currentEvents: [],\n };\n this.threads.set(threadId, state);\n\n // Track consecutive socket errors for this run. Phoenix retries\n // automatically via reconnectAfterMs, but if the connection fails\n // repeatedly we abort the agent — otherwise runAgent() completes\n // normally, finalization events buffer silently on the dead\n // channel, and the client never receives them.\n //\n // Aborting the agent is the single trigger that cascades through\n // the existing error pipeline: runAgent() rejects → catchError\n // pushes RUN_ERROR → finalize calls finalizeRunEvents +\n // removeThread → channel.leave() + socket.disconnect().\n const MAX_CONSECUTIVE_ERRORS = 5;\n let consecutiveErrors = 0;\n\n socket.onError(() => {\n consecutiveErrors++;\n if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS && state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n // Otherwise: Phoenix retries automatically using the exponential\n // backoff schedule configured in createSocket().\n });\n\n socket.onOpen(() => {\n // A successful (re)connection resets the counter so transient\n // network blips don't accumulate across recoveries.\n consecutiveErrors = 0;\n });\n\n // Listen for custom \"stop\" events pushed by the client over the\n // channel. This must be registered before channel.join() so the\n // handler is in place by the time the server starts relaying messages.\n // The client sends the stop event before leaving the channel, so the\n // runner is guaranteed to receive it while still joined.\n channel.on(AG_UI_CHANNEL_EVENT, (payload: BaseEvent) => {\n if (\n payload.type === EventType.CUSTOM &&\n (payload as BaseEvent & { name?: string }).name === \"stop\"\n ) {\n this.stop({ threadId });\n }\n });\n\n channel\n .join()\n .receive(\"ok\", () => {\n this.executeAgentRun(request, state, threadId).subscribe({\n complete: () => observer.complete(),\n });\n })\n .receive(\"error\", (resp) => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: `Failed to join channel: ${JSON.stringify(resp)}`,\n code: \"CHANNEL_JOIN_ERROR\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n })\n .receive(\"timeout\", () => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: \"Timed out joining channel\",\n code: \"CHANNEL_JOIN_TIMEOUT\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n });\n\n return () => {\n this.removeThread(threadId);\n };\n });\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const { threadId } = request;\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channel = socket.channel(`agent:${threadId}`, {\n mode: \"connect\",\n });\n\n // Listen for AG-UI events on a single channel event name.\n channel.on(AG_UI_CHANNEL_EVENT, (payload: BaseEvent) => {\n observer.next(payload);\n\n if (\n payload.type === EventType.RUN_FINISHED ||\n payload.type === EventType.RUN_ERROR\n ) {\n observer.complete();\n }\n });\n\n const cleanup = () => {\n channel.leave();\n socket.disconnect();\n };\n\n channel\n .join()\n .receive(\"ok\", () => {\n // Ask the server to replay history via a CUSTOM event.\n channel.push(EventType.CUSTOM, {\n type: EventType.CUSTOM,\n name: \"connect\",\n value: { threadId },\n });\n })\n .receive(\"error\", (resp) => {\n observer.error(\n new Error(`Failed to join channel: ${JSON.stringify(resp)}`),\n );\n cleanup();\n })\n .receive(\"timeout\", () => {\n observer.error(new Error(\"Timed out joining channel\"));\n cleanup();\n });\n\n return () => {\n cleanup();\n };\n });\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n const state = this.threads.get(request.threadId);\n return Promise.resolve(state?.isRunning ?? false);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const state = this.threads.get(request.threadId);\n if (!state || !state.isRunning || state.stopRequested) {\n return Promise.resolve(false);\n }\n\n state.stopRequested = true;\n\n // Direct local abort — the runtime is the authority.\n if (state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n\n return Promise.resolve(true);\n }\n\n private executeAgentRun(\n request: AgentRunnerRunRequest,\n state: ThreadState,\n threadId: string,\n ): Observable<void> {\n const { currentEvents, channel } = state;\n\n return from(\n request.agent.runAgent(request.input, {\n onEvent: ({ event }: { event: BaseEvent }) => {\n currentEvents.push(event);\n\n // Push to Phoenix channel so frontend WS listeners receive it.\n channel.push(AG_UI_CHANNEL_EVENT, event);\n },\n }),\n ).pipe(\n catchError((error) => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: error instanceof Error ? error.message : String(error),\n } as BaseEvent;\n currentEvents.push(errorEvent);\n channel.push(AG_UI_CHANNEL_EVENT, errorEvent);\n return EMPTY;\n }),\n finalize(() => {\n const appended = finalizeRunEvents(currentEvents, {\n stopRequested: state.stopRequested,\n });\n for (const event of appended) {\n channel.push(AG_UI_CHANNEL_EVENT, event);\n }\n this.removeThread(threadId);\n }),\n );\n }\n\n /**\n * Tear down all resources for a thread: leave the channel,\n * disconnect the per-run socket, and remove the thread state.\n */\n private removeThread(threadId: string): void {\n const state = this.threads.get(threadId);\n if (state) {\n state.channel.leave();\n state.socket.disconnect();\n this.threads.delete(threadId);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"intelligence.mjs","names":[],"sources":["../../src/runner/intelligence.ts"],"sourcesContent":["import {\n AgentRunner,\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"./agent-runner\";\nimport { EMPTY, Observable, from } from \"rxjs\";\nimport { catchError, finalize } from \"rxjs/operators\";\nimport {\n AbstractAgent,\n BaseEvent,\n EventType,\n RunStartedEvent,\n} from \"@ag-ui/client\";\nimport {\n finalizeRunEvents,\n AG_UI_CHANNEL_EVENT,\n phoenixExponentialBackoff,\n} from \"@copilotkitnext/shared\";\nimport { Socket, Channel } from \"phoenix\";\nimport { randomUUID } from \"node:crypto\";\n\nexport interface IntelligenceAgentRunnerOptions {\n /** Phoenix runner websocket URL, e.g. \"ws://localhost:4000/runner\" */\n url: string;\n /** Optional Phoenix socket auth token used during websocket connect. */\n authToken?: string;\n}\n\ninterface ThreadState {\n socket: Socket;\n channel: Channel;\n isRunning: boolean;\n stopRequested: boolean;\n agent: AbstractAgent | null;\n currentEvents: BaseEvent[];\n nextEventSeq: number;\n hasRunStarted: boolean;\n}\n\nexport class IntelligenceAgentRunner extends AgentRunner {\n private options: IntelligenceAgentRunnerOptions;\n private threads = new Map<string, ThreadState>();\n\n constructor(options: IntelligenceAgentRunnerOptions) {\n super();\n // Store config — sockets are created per-run, not eagerly.\n this.options = options;\n }\n\n /**\n * Create a new Phoenix socket with explicit exponential backoff.\n *\n * Each run/connect gets its own socket so that:\n * - A socket failure only affects a single thread, not all threads.\n * - Cleanup is simple: channel.leave() + socket.disconnect() tears\n * down everything for that run with no shared-state concerns.\n * - Each run gets its own independent retry budget.\n *\n * reconnectAfterMs — delay before Phoenix reconnects the WebSocket\n * after an unclean close. 100ms base, doubling up to a 10s cap.\n *\n * rejoinAfterMs — delay before Phoenix re-joins a channel that\n * entered the \"errored\" state. 1s base, doubling up to 30s cap.\n *\n * These are set explicitly because Phoenix's default schedule is a\n * fixed stepped array (not exponential), and any code that calls\n * socket.disconnect() in an onError handler will set\n * closeWasClean = true and reset the reconnect timer — permanently\n * killing retries.\n */\n private createSocket(): Socket {\n const socket = new Socket(this.options.url, {\n ...(this.options.authToken ? { authToken: this.options.authToken } : {}),\n reconnectAfterMs: phoenixExponentialBackoff(100, 10_000),\n rejoinAfterMs: phoenixExponentialBackoff(1_000, 30_000),\n });\n socket.connect();\n return socket;\n }\n\n private createRunnerEventPayload(\n event: BaseEvent,\n request: AgentRunnerRunRequest,\n state: ThreadState,\n ): Record<string, unknown> {\n const canonicalEvent = this.stampRunnerMetadata(event, state);\n const payload = {\n ...(canonicalEvent as Record<string, unknown>),\n };\n\n payload.thread_id ??= request.threadId;\n\n const runId = payload.runId ?? payload.run_id ?? request.input.runId;\n\n if (runId) {\n payload.run_id = runId;\n }\n\n return payload;\n }\n\n private stampRunnerMetadata(event: BaseEvent, state: ThreadState): BaseEvent {\n const eventRecord = event as BaseEvent & {\n metadata?: Record<string, unknown>;\n };\n\n const existingMetadata = eventRecord.metadata ?? {};\n const hasEventId = typeof existingMetadata.cpki_event_id === \"string\";\n const hasEventSeq = typeof existingMetadata.cpki_event_seq === \"number\";\n\n if (hasEventId && hasEventSeq) {\n const eventSeq = existingMetadata.cpki_event_seq as number;\n state.nextEventSeq = Math.max(state.nextEventSeq, eventSeq + 1);\n return eventRecord;\n }\n\n const eventSeq = state.nextEventSeq++;\n\n return {\n ...eventRecord,\n metadata: {\n ...existingMetadata,\n cpki_event_id:\n typeof existingMetadata.cpki_event_id === \"string\"\n ? existingMetadata.cpki_event_id\n : randomUUID(),\n cpki_event_seq: eventSeq,\n },\n };\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n const { threadId, agent, input, joinCode } = request;\n\n const existing = this.threads.get(threadId);\n if (existing?.isRunning) {\n throw new Error(\"Thread already running\");\n }\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channelTopic = joinCode ?? threadId;\n const channel = socket.channel(`ingestion:${channelTopic}`, {\n runId: input.runId,\n });\n\n const state: ThreadState = {\n socket,\n channel,\n isRunning: true,\n stopRequested: false,\n agent,\n currentEvents: [],\n nextEventSeq: 1,\n hasRunStarted: false,\n };\n this.threads.set(threadId, state);\n\n // Track consecutive socket errors for this run. Phoenix retries\n // automatically via reconnectAfterMs, but if the connection fails\n // repeatedly we abort the agent — otherwise runAgent() completes\n // normally, finalization events buffer silently on the dead\n // channel, and the client never receives them.\n //\n // Aborting the agent is the single trigger that cascades through\n // the existing error pipeline: runAgent() rejects → catchError\n // pushes RUN_ERROR → finalize calls finalizeRunEvents +\n // removeThread → channel.leave() + socket.disconnect().\n const MAX_CONSECUTIVE_ERRORS = 5;\n let consecutiveErrors = 0;\n\n socket.onError(() => {\n consecutiveErrors++;\n if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS && state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n // Otherwise: Phoenix retries automatically using the exponential\n // backoff schedule configured in createSocket().\n });\n\n socket.onOpen(() => {\n // A successful (re)connection resets the counter so transient\n // network blips don't accumulate across recoveries.\n consecutiveErrors = 0;\n });\n\n // Listen for custom \"stop\" events pushed by the client over the\n // channel. This must be registered before channel.join() so the\n // handler is in place by the time the server starts relaying messages.\n // The client sends the stop event before leaving the channel, so the\n // runner is guaranteed to receive it while still joined.\n channel.on(AG_UI_CHANNEL_EVENT, (payload: BaseEvent) => {\n if (\n payload.type === EventType.CUSTOM &&\n (payload as BaseEvent & { name?: string }).name === \"stop\"\n ) {\n this.stop({ threadId });\n }\n });\n\n channel\n .join()\n .receive(\"ok\", () => {\n this.executeAgentRun(request, state, threadId).subscribe({\n complete: () => observer.complete(),\n });\n })\n .receive(\"error\", (resp) => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: `Failed to join channel: ${JSON.stringify(resp)}`,\n code: \"CHANNEL_JOIN_ERROR\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n })\n .receive(\"timeout\", () => {\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: \"Timed out joining channel\",\n code: \"CHANNEL_JOIN_TIMEOUT\",\n } as BaseEvent;\n observer.next(errorEvent);\n state.currentEvents.push(errorEvent);\n this.removeThread(threadId);\n observer.complete();\n });\n\n return () => {\n this.removeThread(threadId);\n };\n });\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const { threadId } = request;\n\n return new Observable((observer) => {\n const socket = this.createSocket();\n\n const channel = socket.channel(`thread:${threadId}`);\n\n channel.on(\"ag_ui_event\", (payload: BaseEvent) => {\n observer.next(payload);\n\n if (\n payload.type === EventType.RUN_FINISHED ||\n payload.type === EventType.RUN_ERROR\n ) {\n observer.complete();\n }\n });\n\n const cleanup = () => {\n channel.leave();\n socket.disconnect();\n };\n\n channel\n .join()\n .receive(\"ok\", () => undefined)\n .receive(\"error\", (resp) => {\n observer.error(\n new Error(`Failed to join channel: ${JSON.stringify(resp)}`),\n );\n cleanup();\n })\n .receive(\"timeout\", () => {\n observer.error(new Error(\"Timed out joining channel\"));\n cleanup();\n });\n\n return () => {\n cleanup();\n };\n });\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n const state = this.threads.get(request.threadId);\n return Promise.resolve(state?.isRunning ?? false);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const state = this.threads.get(request.threadId);\n if (!state || !state.isRunning || state.stopRequested) {\n return Promise.resolve(false);\n }\n\n state.stopRequested = true;\n\n // Direct local abort — the runtime is the authority.\n if (state.agent) {\n try {\n state.agent.abortRun();\n } catch {\n // Ignore abort errors.\n }\n }\n\n return Promise.resolve(true);\n }\n\n private executeAgentRun(\n request: AgentRunnerRunRequest,\n state: ThreadState,\n threadId: string,\n ): Observable<void> {\n const { currentEvents, channel } = state;\n const pushCanonicalEvent = (event: BaseEvent): void => {\n const canonicalEvent = this.stampRunnerMetadata(event, state);\n currentEvents.push(canonicalEvent);\n\n if (canonicalEvent.type === EventType.RUN_STARTED) {\n state.hasRunStarted = true;\n }\n\n channel.push(\n \"event\",\n this.createRunnerEventPayload(canonicalEvent, request, state),\n );\n };\n\n const getPersistedInputMessages = () =>\n request.persistedInputMessages ?? request.input.messages;\n\n const buildRunStartedEvent = (\n source?: RunStartedEvent,\n ): RunStartedEvent => {\n const baseInput = source?.input ?? request.input;\n const persistedInputMessages = getPersistedInputMessages();\n\n return {\n ...(source ?? {\n type: EventType.RUN_STARTED,\n threadId: request.threadId,\n runId: request.input.runId,\n }),\n input: {\n ...baseInput,\n ...(persistedInputMessages !== undefined\n ? { messages: persistedInputMessages }\n : {}),\n },\n } as RunStartedEvent;\n };\n\n const ensureRunStarted = (): void => {\n if (!state.hasRunStarted) {\n state.hasRunStarted = true;\n pushCanonicalEvent(buildRunStartedEvent());\n }\n };\n\n return from(\n request.agent.runAgent(request.input, {\n onEvent: ({ event }: { event: BaseEvent }) => {\n if (event.type === EventType.RUN_STARTED) {\n pushCanonicalEvent(buildRunStartedEvent(event as RunStartedEvent));\n return;\n }\n\n ensureRunStarted();\n pushCanonicalEvent(event);\n },\n }),\n ).pipe(\n catchError((error) => {\n ensureRunStarted();\n const errorEvent = {\n type: EventType.RUN_ERROR,\n message: error instanceof Error ? error.message : String(error),\n } as BaseEvent;\n pushCanonicalEvent(errorEvent);\n return EMPTY;\n }),\n finalize(() => {\n ensureRunStarted();\n const appended = finalizeRunEvents(currentEvents, {\n stopRequested: state.stopRequested,\n });\n for (const event of appended) {\n channel.push(\n \"event\",\n this.createRunnerEventPayload(event, request, state),\n );\n }\n this.removeThread(threadId);\n }),\n );\n }\n\n /**\n * Tear down all resources for a thread: leave the channel,\n * disconnect the per-run socket, and remove the thread state.\n *\n * Idempotent — safe to call multiple times for the same threadId\n * (e.g. from join error handlers, finalize, and Observable teardown).\n */\n private removeThread(threadId: string): void {\n const state = this.threads.get(threadId);\n if (!state) {\n return;\n }\n\n // Delete first so concurrent calls see the entry as already removed.\n this.threads.delete(threadId);\n\n try {\n state.channel.leave();\n } catch {\n // Channel may already be closed/left.\n }\n try {\n state.socket.disconnect();\n } catch {\n // Socket may already be disconnected.\n }\n }\n}\n"],"mappings":"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"}
|