@agent-native/core 0.56.1 → 0.58.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/README.md +9 -7
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +66 -10
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +2 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +13 -5
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +24 -4
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +39 -4
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/connectors.d.ts +19 -0
- package/dist/client/chat/connectors.d.ts.map +1 -0
- package/dist/client/chat/connectors.js +992 -0
- package/dist/client/chat/connectors.js.map +1 -0
- package/dist/client/chat/index.d.ts +2 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +2 -0
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/runtime.d.ts +93 -0
- package/dist/client/chat/runtime.d.ts.map +1 -1
- package/dist/client/chat/runtime.js +934 -1
- package/dist/client/chat/runtime.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +48 -3
- package/dist/mcp/build-server.js.map +1 -1
- package/docs/content/actions.md +5 -1
- package/docs/content/agent-surfaces.md +273 -0
- package/docs/content/components.md +46 -17
- package/docs/content/drop-in-agent.md +10 -5
- package/docs/content/embedding-sdk.md +4 -0
- package/docs/content/external-agents.md +1 -0
- package/docs/content/getting-started.md +2 -0
- package/docs/content/harness-agents.md +232 -0
- package/docs/content/key-concepts.md +24 -22
- package/docs/content/mcp-apps.md +1 -1
- package/docs/content/native-chat-ui.md +101 -22
- package/docs/content/plan-plugin.md +27 -1
- package/docs/content/pure-agent-apps.md +2 -1
- package/docs/content/using-your-agent.md +1 -0
- package/docs/content/what-is-agent-native.md +3 -2
- package/package.json +1 -1
|
@@ -1,2 +1,935 @@
|
|
|
1
|
-
|
|
1
|
+
import { agentNativePath } from "../api-path.js";
|
|
2
|
+
import { settleInterruptedToolCalls, } from "../sse-event-processor.js";
|
|
3
|
+
const DEFAULT_RUNTIME_CAPABILITIES = {
|
|
4
|
+
messages: {
|
|
5
|
+
streaming: true,
|
|
6
|
+
history: true,
|
|
7
|
+
structuredContent: true,
|
|
8
|
+
attachments: true,
|
|
9
|
+
},
|
|
10
|
+
tools: {
|
|
11
|
+
events: true,
|
|
12
|
+
hostTools: true,
|
|
13
|
+
resultStreaming: true,
|
|
14
|
+
mcpApps: true,
|
|
15
|
+
},
|
|
16
|
+
sessions: {
|
|
17
|
+
create: true,
|
|
18
|
+
restore: true,
|
|
19
|
+
persistent: true,
|
|
20
|
+
},
|
|
21
|
+
cancellation: {
|
|
22
|
+
abortSignal: true,
|
|
23
|
+
explicitCancel: true,
|
|
24
|
+
},
|
|
25
|
+
models: {
|
|
26
|
+
selectable: true,
|
|
27
|
+
reasoningEffort: true,
|
|
28
|
+
},
|
|
29
|
+
artifacts: {
|
|
30
|
+
files: true,
|
|
31
|
+
links: true,
|
|
32
|
+
progress: true,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
function mergeCapabilities(overrides) {
|
|
36
|
+
return {
|
|
37
|
+
...DEFAULT_RUNTIME_CAPABILITIES,
|
|
38
|
+
...overrides,
|
|
39
|
+
messages: {
|
|
40
|
+
...DEFAULT_RUNTIME_CAPABILITIES.messages,
|
|
41
|
+
...overrides?.messages,
|
|
42
|
+
},
|
|
43
|
+
tools: {
|
|
44
|
+
...DEFAULT_RUNTIME_CAPABILITIES.tools,
|
|
45
|
+
...overrides?.tools,
|
|
46
|
+
events: overrides?.tools?.events ?? DEFAULT_RUNTIME_CAPABILITIES.tools.events,
|
|
47
|
+
},
|
|
48
|
+
sessions: {
|
|
49
|
+
...DEFAULT_RUNTIME_CAPABILITIES.sessions,
|
|
50
|
+
...overrides?.sessions,
|
|
51
|
+
create: overrides?.sessions?.create ??
|
|
52
|
+
DEFAULT_RUNTIME_CAPABILITIES.sessions.create,
|
|
53
|
+
},
|
|
54
|
+
cancellation: {
|
|
55
|
+
...DEFAULT_RUNTIME_CAPABILITIES.cancellation,
|
|
56
|
+
...overrides?.cancellation,
|
|
57
|
+
},
|
|
58
|
+
models: { ...DEFAULT_RUNTIME_CAPABILITIES.models, ...overrides?.models },
|
|
59
|
+
artifacts: {
|
|
60
|
+
...DEFAULT_RUNTIME_CAPABILITIES.artifacts,
|
|
61
|
+
...overrides?.artifacts,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function createRuntimeId(prefix) {
|
|
66
|
+
if (typeof crypto !== "undefined" &&
|
|
67
|
+
typeof crypto.randomUUID === "function") {
|
|
68
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
69
|
+
}
|
|
70
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
71
|
+
}
|
|
72
|
+
function createAbortController(signal) {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
if (!signal)
|
|
75
|
+
return { controller, cleanup: () => { } };
|
|
76
|
+
if (signal.aborted)
|
|
77
|
+
controller.abort();
|
|
78
|
+
const abort = () => controller.abort();
|
|
79
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
80
|
+
return {
|
|
81
|
+
controller,
|
|
82
|
+
cleanup: () => signal.removeEventListener("abort", abort),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function resolveHeaders(headers, input) {
|
|
86
|
+
const resolved = typeof headers === "function" ? await headers(input) : headers;
|
|
87
|
+
return new Headers(resolved);
|
|
88
|
+
}
|
|
89
|
+
function normalizeEndpoint(value) {
|
|
90
|
+
return typeof value === "string" ? value : value.toString();
|
|
91
|
+
}
|
|
92
|
+
function isRuntimeEvent(value) {
|
|
93
|
+
return (!!value &&
|
|
94
|
+
typeof value === "object" &&
|
|
95
|
+
typeof value.type === "string");
|
|
96
|
+
}
|
|
97
|
+
function parseJsonEvent(raw) {
|
|
98
|
+
const trimmed = raw.trim();
|
|
99
|
+
if (!trimmed || trimmed === "[DONE]")
|
|
100
|
+
return null;
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(trimmed);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function* readJsonEventStream(body) {
|
|
109
|
+
const reader = body.getReader();
|
|
110
|
+
const decoder = new TextDecoder();
|
|
111
|
+
let buffer = "";
|
|
112
|
+
let pendingSseData = [];
|
|
113
|
+
const flushSseData = function* () {
|
|
114
|
+
if (pendingSseData.length === 0)
|
|
115
|
+
return;
|
|
116
|
+
const parsed = parseJsonEvent(pendingSseData.join("\n"));
|
|
117
|
+
pendingSseData = [];
|
|
118
|
+
if (parsed)
|
|
119
|
+
yield parsed;
|
|
120
|
+
};
|
|
121
|
+
try {
|
|
122
|
+
while (true) {
|
|
123
|
+
const { done, value } = await reader.read();
|
|
124
|
+
if (done)
|
|
125
|
+
break;
|
|
126
|
+
buffer += decoder.decode(value, { stream: true });
|
|
127
|
+
const lines = buffer.split(/\r?\n/);
|
|
128
|
+
buffer = lines.pop() ?? "";
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
if (line.startsWith("data:")) {
|
|
131
|
+
pendingSseData.push(line.slice(5).trimStart());
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (line.trim() === "") {
|
|
135
|
+
yield* flushSseData();
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const parsed = parseJsonEvent(line);
|
|
139
|
+
if (parsed)
|
|
140
|
+
yield parsed;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (buffer.trim()) {
|
|
144
|
+
const parsed = parseJsonEvent(buffer);
|
|
145
|
+
if (parsed)
|
|
146
|
+
yield parsed;
|
|
147
|
+
}
|
|
148
|
+
yield* flushSseData();
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
try {
|
|
152
|
+
reader.releaseLock();
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Some browser runtimes consider a cancelled stream locked for a tick.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function textResponseEvents(text, sessionId, turnId) {
|
|
160
|
+
const message = {
|
|
161
|
+
id: createRuntimeId("message"),
|
|
162
|
+
role: "assistant",
|
|
163
|
+
content: [],
|
|
164
|
+
};
|
|
165
|
+
return [
|
|
166
|
+
{ type: "message-start", sessionId, turnId, message },
|
|
167
|
+
...(text
|
|
168
|
+
? [
|
|
169
|
+
{
|
|
170
|
+
type: "message-delta",
|
|
171
|
+
sessionId,
|
|
172
|
+
turnId,
|
|
173
|
+
messageId: message.id,
|
|
174
|
+
delta: { type: "text", text },
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
{ type: "message-done", sessionId, turnId, message },
|
|
179
|
+
{ type: "done", sessionId, turnId, reason: "complete" },
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
async function* eventsFromJsonResponse(value, sessionId, turnId) {
|
|
183
|
+
if (Array.isArray(value)) {
|
|
184
|
+
for (const item of value) {
|
|
185
|
+
if (isRuntimeEvent(item))
|
|
186
|
+
yield item;
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!value || typeof value !== "object")
|
|
191
|
+
return;
|
|
192
|
+
const record = value;
|
|
193
|
+
if (Array.isArray(record.events)) {
|
|
194
|
+
for (const item of record.events) {
|
|
195
|
+
if (isRuntimeEvent(item))
|
|
196
|
+
yield item;
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const text = typeof record.message === "string"
|
|
201
|
+
? record.message
|
|
202
|
+
: typeof record.text === "string"
|
|
203
|
+
? record.text
|
|
204
|
+
: "";
|
|
205
|
+
if (text) {
|
|
206
|
+
for (const event of textResponseEvents(text, sessionId, turnId)) {
|
|
207
|
+
yield event;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function defaultMapHttpRuntimeEvent(event, _context) {
|
|
212
|
+
if (!isRuntimeEvent(event))
|
|
213
|
+
return null;
|
|
214
|
+
return event;
|
|
215
|
+
}
|
|
216
|
+
function normalizeMappedEvents(mapped) {
|
|
217
|
+
if (!mapped)
|
|
218
|
+
return [];
|
|
219
|
+
return Array.isArray(mapped)
|
|
220
|
+
? mapped
|
|
221
|
+
: [mapped];
|
|
222
|
+
}
|
|
223
|
+
async function* streamResponseEvents(response, input) {
|
|
224
|
+
const context = {
|
|
225
|
+
sessionId: input.sessionId,
|
|
226
|
+
turnId: input.turnId,
|
|
227
|
+
runId: input.runId,
|
|
228
|
+
};
|
|
229
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
230
|
+
if (response.body && !contentType.includes("application/json")) {
|
|
231
|
+
for await (const raw of readJsonEventStream(response.body)) {
|
|
232
|
+
for (const event of normalizeMappedEvents(input.mapEvent(raw, context))) {
|
|
233
|
+
yield event;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const json = await response.json().catch(() => null);
|
|
239
|
+
for await (const event of eventsFromJsonResponse(json, input.sessionId, input.turnId)) {
|
|
240
|
+
for (const mapped of normalizeMappedEvents(input.mapEvent(event, context))) {
|
|
241
|
+
yield mapped;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function defaultHttpRuntimeRequest(input) {
|
|
246
|
+
return {
|
|
247
|
+
sessionId: input.session.id,
|
|
248
|
+
threadId: input.session.threadId,
|
|
249
|
+
turnId: input.turnId,
|
|
250
|
+
prompt: input.turn.prompt,
|
|
251
|
+
messages: input.turn.messages,
|
|
252
|
+
attachments: input.turn.attachments,
|
|
253
|
+
tools: input.turn.tools,
|
|
254
|
+
model: input.turn.model,
|
|
255
|
+
reasoningEffort: input.turn.reasoningEffort,
|
|
256
|
+
temperature: input.turn.temperature,
|
|
257
|
+
providerOptions: input.turn.providerOptions,
|
|
258
|
+
metadata: input.turn.metadata,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function readErrorText(response) {
|
|
262
|
+
const text = await response.text().catch(() => "");
|
|
263
|
+
if (!text)
|
|
264
|
+
return `HTTP ${response.status}`;
|
|
265
|
+
try {
|
|
266
|
+
const parsed = JSON.parse(text);
|
|
267
|
+
if (typeof parsed.error === "string")
|
|
268
|
+
return parsed.error;
|
|
269
|
+
if (typeof parsed.message === "string")
|
|
270
|
+
return parsed.message;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Keep raw text.
|
|
274
|
+
}
|
|
275
|
+
return text.slice(0, 500);
|
|
276
|
+
}
|
|
277
|
+
export function createHttpAgentChatRuntime(options) {
|
|
278
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
279
|
+
const capabilities = mergeCapabilities(options.capabilities);
|
|
280
|
+
const runtimeId = options.id ?? "external:http";
|
|
281
|
+
const mapEvent = options.mapEvent ??
|
|
282
|
+
defaultMapHttpRuntimeEvent;
|
|
283
|
+
const createSessionObject = (input) => {
|
|
284
|
+
const sessionId = input?.id ?? input?.threadId ?? createRuntimeId("session");
|
|
285
|
+
const summary = {
|
|
286
|
+
id: sessionId,
|
|
287
|
+
runtimeId,
|
|
288
|
+
threadId: input?.threadId,
|
|
289
|
+
title: input?.title,
|
|
290
|
+
status: "idle",
|
|
291
|
+
createdAt: new Date().toISOString(),
|
|
292
|
+
updatedAt: new Date().toISOString(),
|
|
293
|
+
metadata: input?.metadata,
|
|
294
|
+
};
|
|
295
|
+
const startTurn = async (turn) => {
|
|
296
|
+
const turnId = createRuntimeId("turn");
|
|
297
|
+
const { controller, cleanup } = createAbortController(turn.abortSignal);
|
|
298
|
+
const endpoint = typeof options.endpoint === "function"
|
|
299
|
+
? options.endpoint({ session: summary, turn })
|
|
300
|
+
: options.endpoint;
|
|
301
|
+
const headers = await resolveHeaders(options.headers, {
|
|
302
|
+
sessionId,
|
|
303
|
+
turnId,
|
|
304
|
+
});
|
|
305
|
+
if (!headers.has("Content-Type"))
|
|
306
|
+
headers.set("Content-Type", "application/json");
|
|
307
|
+
const response = await fetchImpl(normalizeEndpoint(endpoint), {
|
|
308
|
+
method: options.method ?? "POST",
|
|
309
|
+
headers,
|
|
310
|
+
credentials: options.credentials,
|
|
311
|
+
body: JSON.stringify(options.mapRequest
|
|
312
|
+
? options.mapRequest({ session: summary, turn, turnId })
|
|
313
|
+
: defaultHttpRuntimeRequest({ session: summary, turn, turnId })),
|
|
314
|
+
signal: controller.signal,
|
|
315
|
+
});
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
cleanup();
|
|
318
|
+
throw new Error(await readErrorText(response));
|
|
319
|
+
}
|
|
320
|
+
const runId = response.headers.get("X-Run-Id") ?? undefined;
|
|
321
|
+
const events = (async function* () {
|
|
322
|
+
try {
|
|
323
|
+
yield* streamResponseEvents(response, {
|
|
324
|
+
sessionId,
|
|
325
|
+
turnId,
|
|
326
|
+
runId,
|
|
327
|
+
mapEvent,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
cleanup();
|
|
332
|
+
}
|
|
333
|
+
})();
|
|
334
|
+
return {
|
|
335
|
+
id: turnId,
|
|
336
|
+
sessionId,
|
|
337
|
+
runId,
|
|
338
|
+
events,
|
|
339
|
+
cancel: async (cancelInput) => {
|
|
340
|
+
controller.abort();
|
|
341
|
+
if (!options.cancelEndpoint)
|
|
342
|
+
return { status: "cancelled" };
|
|
343
|
+
const endpoint = typeof options.cancelEndpoint === "function"
|
|
344
|
+
? options.cancelEndpoint({
|
|
345
|
+
...cancelInput,
|
|
346
|
+
sessionId,
|
|
347
|
+
turnId,
|
|
348
|
+
runId,
|
|
349
|
+
})
|
|
350
|
+
: options.cancelEndpoint;
|
|
351
|
+
if (!endpoint)
|
|
352
|
+
return { status: "unsupported" };
|
|
353
|
+
const cancelHeaders = await resolveHeaders(options.headers, {
|
|
354
|
+
sessionId,
|
|
355
|
+
turnId,
|
|
356
|
+
});
|
|
357
|
+
if (!cancelHeaders.has("Content-Type")) {
|
|
358
|
+
cancelHeaders.set("Content-Type", "application/json");
|
|
359
|
+
}
|
|
360
|
+
const cancelResponse = await fetchImpl(normalizeEndpoint(endpoint), {
|
|
361
|
+
method: "POST",
|
|
362
|
+
headers: cancelHeaders,
|
|
363
|
+
credentials: options.credentials,
|
|
364
|
+
body: JSON.stringify({
|
|
365
|
+
sessionId,
|
|
366
|
+
turnId,
|
|
367
|
+
runId,
|
|
368
|
+
reason: cancelInput?.reason ?? "user",
|
|
369
|
+
metadata: cancelInput?.metadata,
|
|
370
|
+
}),
|
|
371
|
+
signal: cancelInput?.abortSignal,
|
|
372
|
+
});
|
|
373
|
+
return cancelResponse.ok
|
|
374
|
+
? { status: "cancelled" }
|
|
375
|
+
: {
|
|
376
|
+
status: "unsupported",
|
|
377
|
+
message: await readErrorText(cancelResponse),
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
};
|
|
382
|
+
return {
|
|
383
|
+
id: sessionId,
|
|
384
|
+
runtimeId,
|
|
385
|
+
threadId: input?.threadId,
|
|
386
|
+
capabilities,
|
|
387
|
+
sendMessage: startTurn,
|
|
388
|
+
startTurn,
|
|
389
|
+
snapshot: () => ({
|
|
390
|
+
...summary,
|
|
391
|
+
status: "idle",
|
|
392
|
+
updatedAt: new Date().toISOString(),
|
|
393
|
+
messages: input?.messages,
|
|
394
|
+
resumeState: input?.resumeState,
|
|
395
|
+
}),
|
|
396
|
+
dispose: () => undefined,
|
|
397
|
+
};
|
|
398
|
+
};
|
|
399
|
+
const runtime = {
|
|
400
|
+
id: runtimeId,
|
|
401
|
+
kind: options.kind ?? "external-agent",
|
|
402
|
+
label: options.label ?? "External agent",
|
|
403
|
+
description: options.description,
|
|
404
|
+
capabilities,
|
|
405
|
+
createSession: createSessionObject,
|
|
406
|
+
restoreSession: (snapshot) => createSessionObject({
|
|
407
|
+
id: snapshot.id,
|
|
408
|
+
threadId: snapshot.threadId,
|
|
409
|
+
title: snapshot.title,
|
|
410
|
+
messages: snapshot.messages,
|
|
411
|
+
resumeState: snapshot.resumeState,
|
|
412
|
+
metadata: snapshot.metadata,
|
|
413
|
+
}),
|
|
414
|
+
sendMessage: async (input) => {
|
|
415
|
+
const session = createSessionObject({
|
|
416
|
+
id: input.sessionId,
|
|
417
|
+
threadId: input.sessionId,
|
|
418
|
+
metadata: input.metadata,
|
|
419
|
+
});
|
|
420
|
+
return session.startTurn(input);
|
|
421
|
+
},
|
|
422
|
+
subscribe: async (input) => {
|
|
423
|
+
if (!options.resumeEndpoint)
|
|
424
|
+
return (async function* () { })();
|
|
425
|
+
const endpoint = typeof options.resumeEndpoint === "function"
|
|
426
|
+
? options.resumeEndpoint(input)
|
|
427
|
+
: options.resumeEndpoint;
|
|
428
|
+
if (!endpoint)
|
|
429
|
+
return (async function* () { })();
|
|
430
|
+
const headers = await resolveHeaders(options.headers, input);
|
|
431
|
+
const response = await fetchImpl(normalizeEndpoint(endpoint), {
|
|
432
|
+
headers,
|
|
433
|
+
credentials: options.credentials,
|
|
434
|
+
signal: input.abortSignal,
|
|
435
|
+
});
|
|
436
|
+
if (!response.ok)
|
|
437
|
+
throw new Error(await readErrorText(response));
|
|
438
|
+
return streamResponseEvents(response, {
|
|
439
|
+
sessionId: input.sessionId ?? "session",
|
|
440
|
+
turnId: input.turnId,
|
|
441
|
+
runId: input.runId,
|
|
442
|
+
mapEvent,
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
resume: async (input) => {
|
|
446
|
+
const sessionId = input.sessionId ?? createRuntimeId("session");
|
|
447
|
+
const events = runtime.subscribe
|
|
448
|
+
? await runtime.subscribe(input)
|
|
449
|
+
: (async function* () { })();
|
|
450
|
+
return {
|
|
451
|
+
id: input.turnId,
|
|
452
|
+
sessionId,
|
|
453
|
+
runId: input.runId,
|
|
454
|
+
events,
|
|
455
|
+
};
|
|
456
|
+
},
|
|
457
|
+
cancel: async (input) => {
|
|
458
|
+
if (!options.cancelEndpoint)
|
|
459
|
+
return { status: "unsupported" };
|
|
460
|
+
const endpoint = typeof options.cancelEndpoint === "function"
|
|
461
|
+
? options.cancelEndpoint(input)
|
|
462
|
+
: options.cancelEndpoint;
|
|
463
|
+
if (!endpoint)
|
|
464
|
+
return { status: "unsupported" };
|
|
465
|
+
const headers = await resolveHeaders(options.headers, input);
|
|
466
|
+
if (!headers.has("Content-Type"))
|
|
467
|
+
headers.set("Content-Type", "application/json");
|
|
468
|
+
const response = await fetchImpl(normalizeEndpoint(endpoint), {
|
|
469
|
+
method: "POST",
|
|
470
|
+
headers,
|
|
471
|
+
credentials: options.credentials,
|
|
472
|
+
body: JSON.stringify(input),
|
|
473
|
+
signal: input.abortSignal,
|
|
474
|
+
});
|
|
475
|
+
return response.ok
|
|
476
|
+
? { status: "cancelled" }
|
|
477
|
+
: { status: "unsupported", message: await readErrorText(response) };
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
return runtime;
|
|
481
|
+
}
|
|
482
|
+
function runtimeMessageText(message) {
|
|
483
|
+
return message.content
|
|
484
|
+
.map((part) => part.type === "text" || part.type === "reasoning" ? part.text : "")
|
|
485
|
+
.filter(Boolean)
|
|
486
|
+
.join("\n");
|
|
487
|
+
}
|
|
488
|
+
function nativeHistoryFromMessages(messages, currentPrompt) {
|
|
489
|
+
const history = (messages ?? [])
|
|
490
|
+
.filter((message) => message.role === "user" || message.role === "assistant")
|
|
491
|
+
.map((message) => ({
|
|
492
|
+
role: message.role,
|
|
493
|
+
content: runtimeMessageText(message),
|
|
494
|
+
}))
|
|
495
|
+
.filter((message) => message.content.trim());
|
|
496
|
+
if (!currentPrompt.trim())
|
|
497
|
+
return history;
|
|
498
|
+
const last = history[history.length - 1];
|
|
499
|
+
return last?.role === "user" && last.content === currentPrompt
|
|
500
|
+
? history.slice(0, -1)
|
|
501
|
+
: history;
|
|
502
|
+
}
|
|
503
|
+
function mapAgentNativeEvent(raw, input) {
|
|
504
|
+
if (!raw || typeof raw !== "object")
|
|
505
|
+
return [];
|
|
506
|
+
const ev = raw;
|
|
507
|
+
const base = {
|
|
508
|
+
sessionId: input.sessionId,
|
|
509
|
+
turnId: input.turnId,
|
|
510
|
+
};
|
|
511
|
+
if (ev.seq !== undefined) {
|
|
512
|
+
base.metadata = {
|
|
513
|
+
seq: ev.seq,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (ev.type === "text") {
|
|
517
|
+
const text = ev.text ?? "";
|
|
518
|
+
const events = [];
|
|
519
|
+
if (!input.text.started) {
|
|
520
|
+
input.text.started = true;
|
|
521
|
+
events.push({
|
|
522
|
+
type: "message-start",
|
|
523
|
+
...base,
|
|
524
|
+
message: {
|
|
525
|
+
id: input.messageId,
|
|
526
|
+
role: "assistant",
|
|
527
|
+
content: [],
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
input.text.value += text;
|
|
532
|
+
events.push({
|
|
533
|
+
type: "message-delta",
|
|
534
|
+
...base,
|
|
535
|
+
messageId: input.messageId,
|
|
536
|
+
delta: { type: "text", text },
|
|
537
|
+
});
|
|
538
|
+
return events;
|
|
539
|
+
}
|
|
540
|
+
if (ev.type === "activity") {
|
|
541
|
+
return [
|
|
542
|
+
{
|
|
543
|
+
type: "status",
|
|
544
|
+
...base,
|
|
545
|
+
message: ev.label ?? ev.tool ?? "Working",
|
|
546
|
+
metadata: ev.tool ? { tool: ev.tool } : undefined,
|
|
547
|
+
},
|
|
548
|
+
];
|
|
549
|
+
}
|
|
550
|
+
if (ev.type === "tool_start") {
|
|
551
|
+
return [
|
|
552
|
+
{
|
|
553
|
+
type: "tool-start",
|
|
554
|
+
...base,
|
|
555
|
+
toolCall: {
|
|
556
|
+
id: ev.id ?? createRuntimeId("tool"),
|
|
557
|
+
name: ev.tool ?? "unknown",
|
|
558
|
+
input: ev.input,
|
|
559
|
+
inputText: ev.input ? JSON.stringify(ev.input) : undefined,
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
];
|
|
563
|
+
}
|
|
564
|
+
if (ev.type === "tool_done") {
|
|
565
|
+
return [
|
|
566
|
+
{
|
|
567
|
+
type: "tool-done",
|
|
568
|
+
...base,
|
|
569
|
+
toolCallId: ev.id ?? "",
|
|
570
|
+
toolName: ev.tool ?? "unknown",
|
|
571
|
+
status: ev.error ? "failed" : "completed",
|
|
572
|
+
result: ev.result,
|
|
573
|
+
resultText: typeof ev.result === "string"
|
|
574
|
+
? ev.result
|
|
575
|
+
: ev.result !== undefined
|
|
576
|
+
? JSON.stringify(ev.result)
|
|
577
|
+
: undefined,
|
|
578
|
+
error: ev.error,
|
|
579
|
+
mcpApp: ev.mcpApp,
|
|
580
|
+
chatUI: ev.chatUI,
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
}
|
|
584
|
+
if (ev.type === "approval_required") {
|
|
585
|
+
return [
|
|
586
|
+
{
|
|
587
|
+
type: "approval-request",
|
|
588
|
+
...base,
|
|
589
|
+
approvalId: ev.approvalKey ?? ev.id ?? createRuntimeId("approval"),
|
|
590
|
+
toolCallId: ev.id,
|
|
591
|
+
toolName: ev.tool,
|
|
592
|
+
message: ev.label ?? "Approve this tool call?",
|
|
593
|
+
input: ev.input,
|
|
594
|
+
},
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
if (ev.type === "error" || ev.type === "missing_api_key") {
|
|
598
|
+
return [
|
|
599
|
+
{
|
|
600
|
+
type: "error",
|
|
601
|
+
...base,
|
|
602
|
+
error: ev.error ?? "Agent chat failed.",
|
|
603
|
+
code: ev.errorCode,
|
|
604
|
+
recoverable: ev.recoverable,
|
|
605
|
+
},
|
|
606
|
+
{ type: "done", ...base, reason: "error" },
|
|
607
|
+
];
|
|
608
|
+
}
|
|
609
|
+
if (ev.type === "done") {
|
|
610
|
+
const message = {
|
|
611
|
+
id: input.messageId,
|
|
612
|
+
role: "assistant",
|
|
613
|
+
content: input.text.value
|
|
614
|
+
? [{ type: "text", text: input.text.value }]
|
|
615
|
+
: [],
|
|
616
|
+
};
|
|
617
|
+
return [
|
|
618
|
+
...(input.text.started
|
|
619
|
+
? [{ type: "message-done", ...base, message }]
|
|
620
|
+
: []),
|
|
621
|
+
{ type: "done", ...base, reason: "complete" },
|
|
622
|
+
];
|
|
623
|
+
}
|
|
624
|
+
return [];
|
|
625
|
+
}
|
|
626
|
+
export function createAgentNativeChatRuntime(options = {}) {
|
|
627
|
+
const apiUrl = options.apiUrl ?? agentNativePath("/_agent-native/agent-chat");
|
|
628
|
+
const runtimeId = options.id ?? "agent-native";
|
|
629
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
630
|
+
return createHttpAgentChatRuntime({
|
|
631
|
+
id: runtimeId,
|
|
632
|
+
kind: "agent-native",
|
|
633
|
+
label: options.label ?? "Agent Native",
|
|
634
|
+
description: options.description ?? "Agent-Native's built-in chat transport.",
|
|
635
|
+
endpoint: apiUrl,
|
|
636
|
+
fetch: fetchImpl,
|
|
637
|
+
headers: async (input) => {
|
|
638
|
+
const headers = await resolveHeaders(options.headers, input);
|
|
639
|
+
headers.set("x-agent-native-surface", options.surface ?? "app");
|
|
640
|
+
return headers;
|
|
641
|
+
},
|
|
642
|
+
capabilities: DEFAULT_RUNTIME_CAPABILITIES,
|
|
643
|
+
mapRequest: ({ session, turn, turnId }) => {
|
|
644
|
+
const prompt = turn.prompt ??
|
|
645
|
+
[...(turn.messages ?? [])]
|
|
646
|
+
.reverse()
|
|
647
|
+
.find((message) => message.role === "user")
|
|
648
|
+
?.content.map((part) => (part.type === "text" ? part.text : ""))
|
|
649
|
+
.join("\n") ??
|
|
650
|
+
"";
|
|
651
|
+
return {
|
|
652
|
+
message: prompt,
|
|
653
|
+
displayMessage: prompt,
|
|
654
|
+
history: nativeHistoryFromMessages(turn.messages, prompt),
|
|
655
|
+
turnId,
|
|
656
|
+
threadId: session.threadId ?? options.threadId,
|
|
657
|
+
...(options.mode ? { mode: options.mode } : {}),
|
|
658
|
+
...((turn.model ?? options.model)
|
|
659
|
+
? { model: turn.model ?? options.model }
|
|
660
|
+
: {}),
|
|
661
|
+
...(options.engine ? { engine: options.engine } : {}),
|
|
662
|
+
...((turn.reasoningEffort ?? options.effort)
|
|
663
|
+
? { effort: turn.reasoningEffort ?? options.effort }
|
|
664
|
+
: {}),
|
|
665
|
+
...(options.browserTabId ? { browserTabId: options.browserTabId } : {}),
|
|
666
|
+
...(options.scope ? { scope: options.scope } : {}),
|
|
667
|
+
...(turn.attachments?.length ? { attachments: turn.attachments } : {}),
|
|
668
|
+
...(turn.metadata ? { metadata: turn.metadata } : {}),
|
|
669
|
+
};
|
|
670
|
+
},
|
|
671
|
+
mapEvent: (() => {
|
|
672
|
+
const states = new Map();
|
|
673
|
+
return (event, context) => {
|
|
674
|
+
const stateKey = context.turnId ?? context.sessionId;
|
|
675
|
+
let state = states.get(stateKey);
|
|
676
|
+
if (!state) {
|
|
677
|
+
state = {
|
|
678
|
+
messageId: createRuntimeId("message"),
|
|
679
|
+
text: { value: "", started: false },
|
|
680
|
+
};
|
|
681
|
+
states.set(stateKey, state);
|
|
682
|
+
}
|
|
683
|
+
const mapped = mapAgentNativeEvent(event, {
|
|
684
|
+
sessionId: context.sessionId,
|
|
685
|
+
turnId: context.turnId,
|
|
686
|
+
messageId: state.messageId,
|
|
687
|
+
text: state.text,
|
|
688
|
+
});
|
|
689
|
+
const type = event && typeof event === "object"
|
|
690
|
+
? event.type
|
|
691
|
+
: undefined;
|
|
692
|
+
if (type === "done" || type === "error" || type === "missing_api_key") {
|
|
693
|
+
states.delete(stateKey);
|
|
694
|
+
}
|
|
695
|
+
return mapped;
|
|
696
|
+
};
|
|
697
|
+
})(),
|
|
698
|
+
cancelEndpoint: (input) => input.runId
|
|
699
|
+
? `${apiUrl}/runs/${encodeURIComponent(input.runId)}/abort`
|
|
700
|
+
: null,
|
|
701
|
+
resumeEndpoint: (input) => input.runId
|
|
702
|
+
? `${apiUrl}/runs/${encodeURIComponent(input.runId)}/events?after=${input.after ?? 0}`
|
|
703
|
+
: null,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
function toContentPartInput(value) {
|
|
707
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
708
|
+
return {};
|
|
709
|
+
const out = {};
|
|
710
|
+
for (const [key, item] of Object.entries(value)) {
|
|
711
|
+
out[key] = typeof item === "string" ? item : JSON.stringify(item);
|
|
712
|
+
}
|
|
713
|
+
return out;
|
|
714
|
+
}
|
|
715
|
+
function toolResultText(value) {
|
|
716
|
+
if (typeof value === "string")
|
|
717
|
+
return value;
|
|
718
|
+
if (value === undefined)
|
|
719
|
+
return "";
|
|
720
|
+
try {
|
|
721
|
+
return JSON.stringify(value);
|
|
722
|
+
}
|
|
723
|
+
catch {
|
|
724
|
+
return String(value);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function applyRuntimeEventToContent(event, content) {
|
|
728
|
+
const typed = event;
|
|
729
|
+
if (typed.type === "message-start") {
|
|
730
|
+
for (const part of typed.message.content) {
|
|
731
|
+
if (part.type === "text" && part.text) {
|
|
732
|
+
content.push({ type: "text", text: part.text });
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return { content: [...content] };
|
|
736
|
+
}
|
|
737
|
+
if (typed.type === "message-delta") {
|
|
738
|
+
if (typed.delta.type === "text") {
|
|
739
|
+
const last = content[content.length - 1];
|
|
740
|
+
if (last?.type === "text") {
|
|
741
|
+
last.text += typed.delta.text;
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
content.push({ type: "text", text: typed.delta.text });
|
|
745
|
+
}
|
|
746
|
+
return { content: [...content] };
|
|
747
|
+
}
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
if (typed.type === "message-done") {
|
|
751
|
+
return { content: [...content] };
|
|
752
|
+
}
|
|
753
|
+
if (typed.type === "tool-start") {
|
|
754
|
+
content.push({
|
|
755
|
+
type: "tool-call",
|
|
756
|
+
toolCallId: typed.toolCall.id,
|
|
757
|
+
toolName: typed.toolCall.name,
|
|
758
|
+
argsText: typed.toolCall.inputText ?? JSON.stringify(typed.toolCall.input ?? {}),
|
|
759
|
+
args: toContentPartInput(typed.toolCall.input),
|
|
760
|
+
});
|
|
761
|
+
return { content: [...content] };
|
|
762
|
+
}
|
|
763
|
+
if (typed.type === "tool-delta") {
|
|
764
|
+
const part = [...content]
|
|
765
|
+
.reverse()
|
|
766
|
+
.find((candidate) => candidate.type === "tool-call" &&
|
|
767
|
+
candidate.toolCallId === typed.toolCallId);
|
|
768
|
+
if (!part)
|
|
769
|
+
return null;
|
|
770
|
+
if (typed.inputTextDelta) {
|
|
771
|
+
part.argsText += typed.inputTextDelta;
|
|
772
|
+
}
|
|
773
|
+
if (typed.resultTextDelta) {
|
|
774
|
+
part.result = `${part.result ?? ""}${typed.resultTextDelta}`;
|
|
775
|
+
}
|
|
776
|
+
return { content: [...content] };
|
|
777
|
+
}
|
|
778
|
+
if (typed.type === "tool-done") {
|
|
779
|
+
const part = [...content]
|
|
780
|
+
.reverse()
|
|
781
|
+
.find((candidate) => candidate.type === "tool-call" &&
|
|
782
|
+
candidate.toolCallId === typed.toolCallId);
|
|
783
|
+
if (part) {
|
|
784
|
+
part.result =
|
|
785
|
+
typed.error ?? typed.resultText ?? toolResultText(typed.result);
|
|
786
|
+
if (typed.mcpApp)
|
|
787
|
+
part.mcpApp = typed.mcpApp;
|
|
788
|
+
if (typed.chatUI)
|
|
789
|
+
part.chatUI = typed.chatUI;
|
|
790
|
+
}
|
|
791
|
+
return { content: [...content] };
|
|
792
|
+
}
|
|
793
|
+
if (typed.type === "approval-request") {
|
|
794
|
+
const part = [...content]
|
|
795
|
+
.reverse()
|
|
796
|
+
.find((candidate) => candidate.type === "tool-call" &&
|
|
797
|
+
(candidate.toolCallId === typed.toolCallId ||
|
|
798
|
+
candidate.toolName === typed.toolName));
|
|
799
|
+
if (part) {
|
|
800
|
+
part.approval = { approvalKey: typed.approvalId };
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
content.push({
|
|
804
|
+
type: "tool-call",
|
|
805
|
+
toolCallId: typed.toolCallId ?? typed.approvalId,
|
|
806
|
+
toolName: typed.toolName ?? "approval",
|
|
807
|
+
argsText: typed.input ? JSON.stringify(typed.input) : "",
|
|
808
|
+
args: toContentPartInput(typed.input),
|
|
809
|
+
approval: { approvalKey: typed.approvalId },
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return { content: [...content] };
|
|
813
|
+
}
|
|
814
|
+
if (typed.type === "error") {
|
|
815
|
+
settleInterruptedToolCalls(content);
|
|
816
|
+
content.push({
|
|
817
|
+
type: "text",
|
|
818
|
+
text: `Something went wrong: ${typed.error}`,
|
|
819
|
+
});
|
|
820
|
+
return {
|
|
821
|
+
content: [...content],
|
|
822
|
+
status: { type: "incomplete", reason: "error" },
|
|
823
|
+
metadata: {
|
|
824
|
+
custom: {
|
|
825
|
+
runError: {
|
|
826
|
+
message: typed.error,
|
|
827
|
+
...(typed.code ? { errorCode: typed.code } : {}),
|
|
828
|
+
...(typed.recoverable ? { recoverable: typed.recoverable } : {}),
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
},
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
if (typed.type === "done") {
|
|
835
|
+
return {
|
|
836
|
+
content: [...content],
|
|
837
|
+
status: typed.reason === "error"
|
|
838
|
+
? { type: "incomplete", reason: "error" }
|
|
839
|
+
: { type: "complete", reason: "stop" },
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
function assistantMessageText(message) {
|
|
845
|
+
return (message.content ?? [])
|
|
846
|
+
.filter((part) => part.type === "text")
|
|
847
|
+
.map((part) => part.text)
|
|
848
|
+
.join("\n");
|
|
849
|
+
}
|
|
850
|
+
function assistantMessagesToRuntimeMessages(messages) {
|
|
851
|
+
return messages
|
|
852
|
+
.filter((message) => message.role === "user" || message.role === "assistant")
|
|
853
|
+
.map((message, index) => {
|
|
854
|
+
const content = [];
|
|
855
|
+
for (const part of message.content ?? []) {
|
|
856
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
857
|
+
content.push({ type: "text", text: part.text });
|
|
858
|
+
}
|
|
859
|
+
else if (part.type === "image" && typeof part.image === "string") {
|
|
860
|
+
content.push({ type: "image", data: part.image });
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
id: `assistant-ui-${index}`,
|
|
865
|
+
role: message.role,
|
|
866
|
+
content,
|
|
867
|
+
};
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
function latestUserPrompt(messages) {
|
|
871
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
872
|
+
const message = messages[i];
|
|
873
|
+
if (message.role !== "user")
|
|
874
|
+
continue;
|
|
875
|
+
return assistantMessageText(message);
|
|
876
|
+
}
|
|
877
|
+
return "";
|
|
878
|
+
}
|
|
879
|
+
export function createAgentChatRuntimeAdapter(runtime, options = {}) {
|
|
880
|
+
let sessionPromise = null;
|
|
881
|
+
const getSession = () => {
|
|
882
|
+
sessionPromise ??= Promise.resolve(runtime.createSession({
|
|
883
|
+
id: options.sessionId ?? options.threadId,
|
|
884
|
+
threadId: options.threadId,
|
|
885
|
+
metadata: options.metadata,
|
|
886
|
+
}));
|
|
887
|
+
return sessionPromise;
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
async *run({ messages, abortSignal }) {
|
|
891
|
+
const adapterMessages = messages;
|
|
892
|
+
const session = await getSession();
|
|
893
|
+
const prompt = latestUserPrompt(adapterMessages);
|
|
894
|
+
const turn = await session.startTurn({
|
|
895
|
+
prompt,
|
|
896
|
+
messages: assistantMessagesToRuntimeMessages(adapterMessages),
|
|
897
|
+
model: options.modelRef?.current,
|
|
898
|
+
reasoningEffort: options.effortRef?.current,
|
|
899
|
+
abortSignal,
|
|
900
|
+
metadata: options.metadata,
|
|
901
|
+
});
|
|
902
|
+
const content = [];
|
|
903
|
+
try {
|
|
904
|
+
for await (const event of turn.events) {
|
|
905
|
+
const result = applyRuntimeEventToContent(event, content);
|
|
906
|
+
if (result) {
|
|
907
|
+
const metadata = (result.metadata ?? {});
|
|
908
|
+
const custom = metadata.custom && typeof metadata.custom === "object"
|
|
909
|
+
? metadata.custom
|
|
910
|
+
: {};
|
|
911
|
+
yield {
|
|
912
|
+
...result,
|
|
913
|
+
metadata: {
|
|
914
|
+
...metadata,
|
|
915
|
+
custom: {
|
|
916
|
+
...custom,
|
|
917
|
+
runtimeId: runtime.id,
|
|
918
|
+
...(turn.runId ? { runId: turn.runId } : {}),
|
|
919
|
+
},
|
|
920
|
+
},
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
catch (error) {
|
|
926
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
927
|
+
await turn.cancel?.({ reason: "abort" });
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
throw error;
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
};
|
|
934
|
+
}
|
|
2
935
|
//# sourceMappingURL=runtime.js.map
|