@runtypelabs/timely-a2a 0.3.3
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/LICENSE +21 -0
- package/README.md +404 -0
- package/dist/chunk-KIZQKOZR.js +1261 -0
- package/dist/chunk-KIZQKOZR.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +215 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +402 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/vercel/index.d.ts +149 -0
- package/dist/vercel/index.js +1005 -0
- package/dist/vercel/index.js.map +1 -0
- package/package.json +87 -0
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
// src/vercel/handlers.ts
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
|
|
4
|
+
// src/types.ts
|
|
5
|
+
var JSON_RPC_ERRORS = {
|
|
6
|
+
PARSE_ERROR: -32700,
|
|
7
|
+
INVALID_REQUEST: -32600,
|
|
8
|
+
METHOD_NOT_FOUND: -32601,
|
|
9
|
+
INVALID_PARAMS: -32602,
|
|
10
|
+
INTERNAL_ERROR: -32603,
|
|
11
|
+
// A2A spec error codes
|
|
12
|
+
TASK_NOT_FOUND: -32001,
|
|
13
|
+
TASK_NOT_CANCELABLE: -32002,
|
|
14
|
+
PUSH_NOTIFICATION_NOT_SUPPORTED: -32003,
|
|
15
|
+
UNSUPPORTED_OPERATION: -32004,
|
|
16
|
+
CONTENT_TYPE_NOT_SUPPORTED: -32005,
|
|
17
|
+
INVALID_AGENT_RESPONSE: -32006,
|
|
18
|
+
/** @deprecated Use TASK_NOT_CANCELABLE */
|
|
19
|
+
SKILL_NOT_FOUND: -32002,
|
|
20
|
+
/** @deprecated */
|
|
21
|
+
UNAUTHORIZED: -32003,
|
|
22
|
+
/** @deprecated */
|
|
23
|
+
RATE_LIMITED: -32004,
|
|
24
|
+
/** @deprecated */
|
|
25
|
+
TASK_CANCELED: -32005
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/executor.ts
|
|
29
|
+
import { streamText, generateText, tool, jsonSchema, stepCountIs } from "ai";
|
|
30
|
+
import { createGateway } from "@ai-sdk/gateway";
|
|
31
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
32
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
33
|
+
|
|
34
|
+
// src/time-executor.ts
|
|
35
|
+
import {
|
|
36
|
+
format,
|
|
37
|
+
addDays,
|
|
38
|
+
addWeeks,
|
|
39
|
+
addMonths,
|
|
40
|
+
addBusinessDays,
|
|
41
|
+
differenceInDays,
|
|
42
|
+
differenceInHours,
|
|
43
|
+
differenceInMinutes,
|
|
44
|
+
isWeekend,
|
|
45
|
+
isBefore,
|
|
46
|
+
parseISO
|
|
47
|
+
} from "date-fns";
|
|
48
|
+
import { formatInTimeZone } from "date-fns-tz";
|
|
49
|
+
function createResponse(result, source) {
|
|
50
|
+
return {
|
|
51
|
+
result,
|
|
52
|
+
computed: { method: "deterministic", source },
|
|
53
|
+
usage: "Use this value directly. Do not recalculate."
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function extractInput(message) {
|
|
57
|
+
for (const part of message.parts) {
|
|
58
|
+
if ((part.type === "data" || part.data !== void 0) && part.data) {
|
|
59
|
+
return part.data;
|
|
60
|
+
}
|
|
61
|
+
if ((part.type === "text" || part.text !== void 0) && part.text) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(part.text);
|
|
64
|
+
} catch {
|
|
65
|
+
return { expression: part.text };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
async function executeTimeSkill(context) {
|
|
72
|
+
const input = extractInput(context.message);
|
|
73
|
+
let response;
|
|
74
|
+
switch (context.skill.id) {
|
|
75
|
+
case "time/now": {
|
|
76
|
+
const tz = input.timezone || "UTC";
|
|
77
|
+
const now = /* @__PURE__ */ new Date();
|
|
78
|
+
response = createResponse(
|
|
79
|
+
{
|
|
80
|
+
timestamp: now.toISOString(),
|
|
81
|
+
timezone: tz,
|
|
82
|
+
formatted: formatInTimeZone(now, tz, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
83
|
+
human: formatInTimeZone(now, tz, "EEEE, MMMM d, yyyy h:mm a zzz"),
|
|
84
|
+
day: formatInTimeZone(now, tz, "EEEE"),
|
|
85
|
+
offset: formatInTimeZone(now, tz, "xxx")
|
|
86
|
+
},
|
|
87
|
+
"system_clock"
|
|
88
|
+
);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "time/day_of_week": {
|
|
92
|
+
const dateStr = input.date;
|
|
93
|
+
if (!dateStr) throw new Error("Missing required field: date");
|
|
94
|
+
const date = parseISO(dateStr);
|
|
95
|
+
if (isNaN(date.getTime())) throw new Error(`Invalid date: ${dateStr}`);
|
|
96
|
+
response = createResponse(
|
|
97
|
+
{
|
|
98
|
+
date: dateStr,
|
|
99
|
+
day: format(date, "EEEE"),
|
|
100
|
+
// @snake-case-ok: A2A protocol response field
|
|
101
|
+
day_index: date.getDay(),
|
|
102
|
+
// @snake-case-ok: A2A protocol response field
|
|
103
|
+
is_weekend: isWeekend(date)
|
|
104
|
+
},
|
|
105
|
+
"date_arithmetic"
|
|
106
|
+
);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "time/add": {
|
|
110
|
+
const baseStr = input.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
111
|
+
const base = parseISO(baseStr);
|
|
112
|
+
if (isNaN(base.getTime())) throw new Error(`Invalid date: ${baseStr}`);
|
|
113
|
+
let result = base;
|
|
114
|
+
if (input.days) result = addDays(result, input.days);
|
|
115
|
+
if (input.weeks) result = addWeeks(result, input.weeks);
|
|
116
|
+
if (input.months) result = addMonths(result, input.months);
|
|
117
|
+
response = createResponse(
|
|
118
|
+
{
|
|
119
|
+
original: baseStr,
|
|
120
|
+
computed: format(result, "yyyy-MM-dd"),
|
|
121
|
+
day: format(result, "EEEE"),
|
|
122
|
+
operation: { days: input.days, weeks: input.weeks, months: input.months }
|
|
123
|
+
},
|
|
124
|
+
"date_arithmetic"
|
|
125
|
+
);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "time/business_days": {
|
|
129
|
+
const baseStr = input.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
130
|
+
const days = input.days;
|
|
131
|
+
if (typeof days !== "number") throw new Error("Missing required field: days");
|
|
132
|
+
const base = parseISO(baseStr);
|
|
133
|
+
if (isNaN(base.getTime())) throw new Error(`Invalid date: ${baseStr}`);
|
|
134
|
+
const result = addBusinessDays(base, days);
|
|
135
|
+
response = createResponse(
|
|
136
|
+
{
|
|
137
|
+
original: baseStr,
|
|
138
|
+
computed: format(result, "yyyy-MM-dd"),
|
|
139
|
+
day: format(result, "EEEE"),
|
|
140
|
+
// @snake-case-ok: A2A protocol response field
|
|
141
|
+
business_days_added: days
|
|
142
|
+
},
|
|
143
|
+
"date_arithmetic"
|
|
144
|
+
);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "time/diff": {
|
|
148
|
+
const from = parseISO(input.from);
|
|
149
|
+
const to = parseISO(input.to);
|
|
150
|
+
if (isNaN(from.getTime())) throw new Error(`Invalid from date: ${input.from}`);
|
|
151
|
+
if (isNaN(to.getTime())) throw new Error(`Invalid to date: ${input.to}`);
|
|
152
|
+
const days = differenceInDays(to, from);
|
|
153
|
+
const hours = differenceInHours(to, from);
|
|
154
|
+
const minutes = differenceInMinutes(to, from);
|
|
155
|
+
response = createResponse(
|
|
156
|
+
{
|
|
157
|
+
from: input.from,
|
|
158
|
+
to: input.to,
|
|
159
|
+
difference: { days, hours, minutes },
|
|
160
|
+
human: `${Math.abs(days)} days`
|
|
161
|
+
},
|
|
162
|
+
"date_arithmetic"
|
|
163
|
+
);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case "time/is_past": {
|
|
167
|
+
const ts = input.timestamp;
|
|
168
|
+
if (!ts) throw new Error("Missing required field: timestamp");
|
|
169
|
+
const check = parseISO(ts);
|
|
170
|
+
if (isNaN(check.getTime())) throw new Error(`Invalid timestamp: ${ts}`);
|
|
171
|
+
const now = /* @__PURE__ */ new Date();
|
|
172
|
+
response = createResponse(
|
|
173
|
+
{
|
|
174
|
+
timestamp: ts,
|
|
175
|
+
// @snake-case-ok: A2A protocol response field
|
|
176
|
+
is_past: isBefore(check, now),
|
|
177
|
+
// @snake-case-ok: A2A protocol response field
|
|
178
|
+
is_future: isBefore(now, check),
|
|
179
|
+
// @snake-case-ok: A2A protocol response field
|
|
180
|
+
checked_at: now.toISOString()
|
|
181
|
+
},
|
|
182
|
+
"system_clock"
|
|
183
|
+
);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "time/convert": {
|
|
187
|
+
const ts = input.timestamp;
|
|
188
|
+
const toTz = input.to;
|
|
189
|
+
if (!ts) throw new Error("Missing required field: timestamp");
|
|
190
|
+
if (!toTz) throw new Error("Missing required field: to (timezone)");
|
|
191
|
+
const date = parseISO(ts);
|
|
192
|
+
if (isNaN(date.getTime())) throw new Error(`Invalid timestamp: ${ts}`);
|
|
193
|
+
response = createResponse(
|
|
194
|
+
{
|
|
195
|
+
original: ts,
|
|
196
|
+
converted: formatInTimeZone(date, toTz, "yyyy-MM-dd'T'HH:mm:ssXXX"),
|
|
197
|
+
timezone: toTz,
|
|
198
|
+
human: formatInTimeZone(date, toTz, "EEEE, MMMM d, yyyy h:mm a zzz")
|
|
199
|
+
},
|
|
200
|
+
"date_arithmetic"
|
|
201
|
+
);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case "time/parse": {
|
|
205
|
+
const expr = input.expression?.toLowerCase();
|
|
206
|
+
const tz = input.timezone || "UTC";
|
|
207
|
+
const ref = input.reference ? parseISO(input.reference) : /* @__PURE__ */ new Date();
|
|
208
|
+
if (!expr) throw new Error("Missing required field: expression");
|
|
209
|
+
let result = null;
|
|
210
|
+
if (expr === "today") {
|
|
211
|
+
result = ref;
|
|
212
|
+
} else if (expr === "tomorrow") {
|
|
213
|
+
result = addDays(ref, 1);
|
|
214
|
+
} else if (expr === "yesterday") {
|
|
215
|
+
result = addDays(ref, -1);
|
|
216
|
+
} else if (expr.startsWith("next ")) {
|
|
217
|
+
const dayName = expr.replace("next ", "").split(" ")[0];
|
|
218
|
+
const days = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
219
|
+
const targetDay = days.indexOf(dayName);
|
|
220
|
+
if (targetDay >= 0) {
|
|
221
|
+
let daysToAdd = targetDay - ref.getDay();
|
|
222
|
+
if (daysToAdd <= 0) daysToAdd += 7;
|
|
223
|
+
result = addDays(ref, daysToAdd);
|
|
224
|
+
}
|
|
225
|
+
} else if (expr.includes("in ") && expr.includes(" day")) {
|
|
226
|
+
const match = expr.match(/in (\d+) days?/);
|
|
227
|
+
if (match) result = addDays(ref, parseInt(match[1]));
|
|
228
|
+
} else if (expr.includes("in ") && expr.includes(" week")) {
|
|
229
|
+
const match = expr.match(/in (\d+) weeks?/);
|
|
230
|
+
if (match) result = addWeeks(ref, parseInt(match[1]));
|
|
231
|
+
}
|
|
232
|
+
if (!result) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`Could not parse expression: "${expr}". Supported: today, tomorrow, yesterday, next [day], in N days/weeks`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
response = createResponse(
|
|
238
|
+
{
|
|
239
|
+
expression: input.expression,
|
|
240
|
+
resolved: format(result, "yyyy-MM-dd"),
|
|
241
|
+
day: format(result, "EEEE"),
|
|
242
|
+
timezone: tz,
|
|
243
|
+
// @snake-case-ok: A2A protocol response field
|
|
244
|
+
reference_used: ref.toISOString()
|
|
245
|
+
},
|
|
246
|
+
"date_arithmetic"
|
|
247
|
+
);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
default:
|
|
251
|
+
throw new Error(`Unknown time skill: ${context.skill.id}`);
|
|
252
|
+
}
|
|
253
|
+
const text = JSON.stringify(response, null, 2);
|
|
254
|
+
return {
|
|
255
|
+
text,
|
|
256
|
+
artifacts: [
|
|
257
|
+
{
|
|
258
|
+
name: "response",
|
|
259
|
+
parts: [{ type: "text", text }]
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/executor.ts
|
|
266
|
+
var DEFAULT_SYSTEM_PROMPTS = {
|
|
267
|
+
chat: `You are Timely, a time-focused AI assistant. You help with time, date, timezone, and scheduling questions. Use the available time tools to provide accurate answers. If asked about topics unrelated to time, dates, timezones, or scheduling, politely explain that you specialize in time-related queries and suggest rephrasing the question in a time-related context.`
|
|
268
|
+
};
|
|
269
|
+
var CHAT_SKILL_ID = "chat";
|
|
270
|
+
var TOOL_STOP_CONDITION = stepCountIs(5);
|
|
271
|
+
var TOOL_TAG = "tool";
|
|
272
|
+
var LLM_TAG = "llm";
|
|
273
|
+
function extractTextFromMessage(message) {
|
|
274
|
+
return message.parts.filter((p) => (p.type === "text" || p.text !== void 0) && p.text).map((p) => p.text).join("\n");
|
|
275
|
+
}
|
|
276
|
+
function createLLMProvider(config) {
|
|
277
|
+
const gatewayKey = process.env.AI_GATEWAY_API_KEY;
|
|
278
|
+
if (gatewayKey) {
|
|
279
|
+
const gateway = createGateway({ apiKey: gatewayKey });
|
|
280
|
+
const modelId = config.gatewayModel || (config.model.includes("/") ? config.model : `${config.provider}/${config.model}`);
|
|
281
|
+
return gateway(modelId);
|
|
282
|
+
}
|
|
283
|
+
if (config.model.includes("/")) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Model "${config.model}" requires Vercel AI Gateway. Set AI_GATEWAY_API_KEY to use gateway models, or use a direct provider model (e.g., --model gpt-4o-mini --provider openai).`
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
const apiKey = config.apiKey || process.env[`${config.provider.toUpperCase()}_API_KEY`];
|
|
289
|
+
if (!apiKey) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`API key not provided. Set AI_GATEWAY_API_KEY for Vercel AI Gateway, or ${config.provider.toUpperCase()}_API_KEY for direct provider access.`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
switch (config.provider) {
|
|
295
|
+
case "openai":
|
|
296
|
+
return createOpenAI({ apiKey })(config.model);
|
|
297
|
+
case "anthropic":
|
|
298
|
+
return createAnthropic({ apiKey })(config.model);
|
|
299
|
+
default:
|
|
300
|
+
throw new Error(`Unsupported LLM provider: ${config.provider}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function getSystemPrompt(skill) {
|
|
304
|
+
if (skill.systemPrompt) {
|
|
305
|
+
return skill.systemPrompt;
|
|
306
|
+
}
|
|
307
|
+
return DEFAULT_SYSTEM_PROMPTS[skill.id] || DEFAULT_SYSTEM_PROMPTS.chat;
|
|
308
|
+
}
|
|
309
|
+
function sanitizeToolName(skillId) {
|
|
310
|
+
let name = skillId.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
|
|
311
|
+
if (!name) {
|
|
312
|
+
name = "tool";
|
|
313
|
+
}
|
|
314
|
+
if (/^[0-9]/.test(name)) {
|
|
315
|
+
name = `tool_${name}`;
|
|
316
|
+
}
|
|
317
|
+
return name;
|
|
318
|
+
}
|
|
319
|
+
function makeUniqueToolName(skillId, usedNames) {
|
|
320
|
+
const baseName = sanitizeToolName(skillId);
|
|
321
|
+
if (!usedNames.has(baseName)) {
|
|
322
|
+
usedNames.add(baseName);
|
|
323
|
+
return baseName;
|
|
324
|
+
}
|
|
325
|
+
let counter = 1;
|
|
326
|
+
let candidate = `${baseName}_${counter}`;
|
|
327
|
+
while (usedNames.has(candidate)) {
|
|
328
|
+
counter += 1;
|
|
329
|
+
candidate = `${baseName}_${counter}`;
|
|
330
|
+
}
|
|
331
|
+
usedNames.add(candidate);
|
|
332
|
+
return candidate;
|
|
333
|
+
}
|
|
334
|
+
function toToolInput(params) {
|
|
335
|
+
if (params && typeof params === "object" && !Array.isArray(params)) {
|
|
336
|
+
return params;
|
|
337
|
+
}
|
|
338
|
+
return { input: params };
|
|
339
|
+
}
|
|
340
|
+
function parseToolResult(text) {
|
|
341
|
+
try {
|
|
342
|
+
return JSON.parse(text);
|
|
343
|
+
} catch {
|
|
344
|
+
return { text };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function executeCatalogToolSkill(parentContext, skill, params) {
|
|
348
|
+
if (skill.id.startsWith("time/")) {
|
|
349
|
+
const toolContext = {
|
|
350
|
+
taskId: `${parentContext.taskId}_tool_${skill.id}`,
|
|
351
|
+
contextId: parentContext.contextId,
|
|
352
|
+
skill,
|
|
353
|
+
message: {
|
|
354
|
+
role: "user",
|
|
355
|
+
parts: [{ type: "data", data: toToolInput(params) }]
|
|
356
|
+
},
|
|
357
|
+
metadata: parentContext.metadata
|
|
358
|
+
};
|
|
359
|
+
const result = await executeTimeSkill(toolContext);
|
|
360
|
+
return parseToolResult(result.text);
|
|
361
|
+
}
|
|
362
|
+
throw new Error(`Unsupported tool skill: ${skill.id}`);
|
|
363
|
+
}
|
|
364
|
+
function buildChatTools(context, skillsCatalog) {
|
|
365
|
+
if (context.skill.id !== CHAT_SKILL_ID || !skillsCatalog || skillsCatalog.length === 0) {
|
|
366
|
+
return {};
|
|
367
|
+
}
|
|
368
|
+
const tools = {};
|
|
369
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
370
|
+
for (const skill of skillsCatalog) {
|
|
371
|
+
if (skill.id === context.skill.id) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const tags = skill.tags ?? [];
|
|
375
|
+
if (!tags.includes(TOOL_TAG) || tags.includes(LLM_TAG)) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (!skill.id.startsWith("time/")) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const toolName = makeUniqueToolName(skill.id, usedNames);
|
|
382
|
+
const schema = skill.inputSchema ?? {
|
|
383
|
+
type: "object",
|
|
384
|
+
properties: {},
|
|
385
|
+
additionalProperties: true
|
|
386
|
+
};
|
|
387
|
+
tools[toolName] = tool({
|
|
388
|
+
description: skill.description || `Execute ${skill.name}`,
|
|
389
|
+
inputSchema: jsonSchema(schema),
|
|
390
|
+
execute: async (params) => executeCatalogToolSkill(context, skill, params)
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return tools;
|
|
394
|
+
}
|
|
395
|
+
async function executeTask(context, llmConfig, skillsCatalog) {
|
|
396
|
+
const model = createLLMProvider(llmConfig);
|
|
397
|
+
const userMessage = extractTextFromMessage(context.message);
|
|
398
|
+
const systemPrompt = getSystemPrompt(context.skill);
|
|
399
|
+
const chatTools = buildChatTools(context, skillsCatalog);
|
|
400
|
+
const hasChatTools = Object.keys(chatTools).length > 0;
|
|
401
|
+
const result = await generateText({
|
|
402
|
+
model,
|
|
403
|
+
system: systemPrompt,
|
|
404
|
+
prompt: userMessage,
|
|
405
|
+
temperature: llmConfig.temperature ?? 0.7,
|
|
406
|
+
maxOutputTokens: llmConfig.maxOutputTokens ?? 4096,
|
|
407
|
+
...hasChatTools ? { tools: chatTools, stopWhen: TOOL_STOP_CONDITION } : {}
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
text: result.text,
|
|
411
|
+
artifacts: [
|
|
412
|
+
{
|
|
413
|
+
name: "response",
|
|
414
|
+
parts: [{ type: "text", text: result.text }]
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
async function executeTaskStreaming(context, llmConfig, callbacks, skillsCatalog) {
|
|
420
|
+
const model = createLLMProvider(llmConfig);
|
|
421
|
+
const userMessage = extractTextFromMessage(context.message);
|
|
422
|
+
const systemPrompt = getSystemPrompt(context.skill);
|
|
423
|
+
const chatTools = buildChatTools(context, skillsCatalog);
|
|
424
|
+
const hasChatTools = Object.keys(chatTools).length > 0;
|
|
425
|
+
const result = streamText({
|
|
426
|
+
model,
|
|
427
|
+
system: systemPrompt,
|
|
428
|
+
prompt: userMessage,
|
|
429
|
+
temperature: llmConfig.temperature ?? 0.7,
|
|
430
|
+
maxOutputTokens: llmConfig.maxOutputTokens ?? 4096,
|
|
431
|
+
...hasChatTools ? { tools: chatTools, stopWhen: TOOL_STOP_CONDITION } : {},
|
|
432
|
+
onError: async ({ error }) => {
|
|
433
|
+
await callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
for await (const chunk of result.textStream) {
|
|
437
|
+
await callbacks.onChunk(chunk);
|
|
438
|
+
}
|
|
439
|
+
await callbacks.onComplete();
|
|
440
|
+
}
|
|
441
|
+
async function executeEcho(context) {
|
|
442
|
+
const userMessage = extractTextFromMessage(context.message);
|
|
443
|
+
return {
|
|
444
|
+
text: `Echo from ${context.skill.name}: ${userMessage}`,
|
|
445
|
+
artifacts: [
|
|
446
|
+
{
|
|
447
|
+
name: "response",
|
|
448
|
+
parts: [{ type: "text", text: `Echo from ${context.skill.name}: ${userMessage}` }]
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
async function executeEchoStreaming(context, callbacks) {
|
|
454
|
+
const userMessage = extractTextFromMessage(context.message);
|
|
455
|
+
const response = `Echo from ${context.skill.name}: ${userMessage}`;
|
|
456
|
+
try {
|
|
457
|
+
const words = response.split(" ");
|
|
458
|
+
for (let i = 0; i < words.length; i++) {
|
|
459
|
+
const chunk = (i === 0 ? "" : " ") + words[i];
|
|
460
|
+
await callbacks.onChunk(chunk);
|
|
461
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
462
|
+
}
|
|
463
|
+
await callbacks.onComplete();
|
|
464
|
+
} catch (error) {
|
|
465
|
+
await callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/default-skills.ts
|
|
470
|
+
var DEFAULT_SKILLS = [
|
|
471
|
+
// Time tools (deterministic — no LLM)
|
|
472
|
+
{
|
|
473
|
+
id: "time/now",
|
|
474
|
+
name: "Current Time",
|
|
475
|
+
description: "Returns current UTC time with optional timezone conversion",
|
|
476
|
+
inputSchema: {
|
|
477
|
+
type: "object",
|
|
478
|
+
properties: {
|
|
479
|
+
timezone: { type: "string", description: "IANA timezone name (e.g. America/New_York)" }
|
|
480
|
+
},
|
|
481
|
+
additionalProperties: false
|
|
482
|
+
},
|
|
483
|
+
tags: ["deterministic", "time", "tool"]
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "time/parse",
|
|
487
|
+
name: "Parse Date Expression",
|
|
488
|
+
description: 'Converts "next Tuesday at 3pm" to ISO timestamp',
|
|
489
|
+
inputSchema: {
|
|
490
|
+
type: "object",
|
|
491
|
+
properties: {
|
|
492
|
+
expression: { type: "string", description: "Natural language date expression" },
|
|
493
|
+
timezone: { type: "string", description: "IANA timezone name (default: UTC)" },
|
|
494
|
+
reference: { type: "string", description: "Reference ISO timestamp", format: "date-time" }
|
|
495
|
+
},
|
|
496
|
+
required: ["expression"],
|
|
497
|
+
additionalProperties: false
|
|
498
|
+
},
|
|
499
|
+
tags: ["deterministic", "time", "tool"]
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: "time/convert",
|
|
503
|
+
name: "Convert Timezone",
|
|
504
|
+
description: "Converts a timestamp between timezones",
|
|
505
|
+
inputSchema: {
|
|
506
|
+
type: "object",
|
|
507
|
+
properties: {
|
|
508
|
+
timestamp: { type: "string", description: "ISO timestamp to convert", format: "date-time" },
|
|
509
|
+
to: { type: "string", description: "Target IANA timezone name" }
|
|
510
|
+
},
|
|
511
|
+
required: ["timestamp", "to"],
|
|
512
|
+
additionalProperties: false
|
|
513
|
+
},
|
|
514
|
+
tags: ["deterministic", "time", "tool"]
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
id: "time/add",
|
|
518
|
+
name: "Date Arithmetic",
|
|
519
|
+
description: "Add days, weeks, or months to a date",
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: {
|
|
523
|
+
date: { type: "string", description: "Base ISO date (defaults to today)" },
|
|
524
|
+
days: { type: "number", description: "Days to add (can be negative)" },
|
|
525
|
+
weeks: { type: "number", description: "Weeks to add (can be negative)" },
|
|
526
|
+
months: { type: "number", description: "Months to add (can be negative)" }
|
|
527
|
+
},
|
|
528
|
+
additionalProperties: false
|
|
529
|
+
},
|
|
530
|
+
tags: ["deterministic", "time", "tool"]
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
id: "time/diff",
|
|
534
|
+
name: "Date Difference",
|
|
535
|
+
description: "Calculate duration between two dates",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
from: { type: "string", description: "Start ISO timestamp", format: "date-time" },
|
|
540
|
+
to: { type: "string", description: "End ISO timestamp", format: "date-time" }
|
|
541
|
+
},
|
|
542
|
+
required: ["from", "to"],
|
|
543
|
+
additionalProperties: false
|
|
544
|
+
},
|
|
545
|
+
tags: ["deterministic", "time", "tool"]
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
id: "time/day_of_week",
|
|
549
|
+
name: "Day of Week",
|
|
550
|
+
description: "Returns the day of the week for a given date",
|
|
551
|
+
inputSchema: {
|
|
552
|
+
type: "object",
|
|
553
|
+
properties: {
|
|
554
|
+
date: { type: "string", description: "ISO date string" }
|
|
555
|
+
},
|
|
556
|
+
required: ["date"],
|
|
557
|
+
additionalProperties: false
|
|
558
|
+
},
|
|
559
|
+
tags: ["deterministic", "time", "tool"]
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: "time/is_past",
|
|
563
|
+
name: "Is Past",
|
|
564
|
+
description: "Checks whether a timestamp is in the past",
|
|
565
|
+
inputSchema: {
|
|
566
|
+
type: "object",
|
|
567
|
+
properties: {
|
|
568
|
+
timestamp: { type: "string", description: "ISO timestamp to check", format: "date-time" }
|
|
569
|
+
},
|
|
570
|
+
required: ["timestamp"],
|
|
571
|
+
additionalProperties: false
|
|
572
|
+
},
|
|
573
|
+
tags: ["deterministic", "time", "tool"]
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
id: "time/business_days",
|
|
577
|
+
name: "Business Days",
|
|
578
|
+
description: "Add or subtract business days from a date",
|
|
579
|
+
inputSchema: {
|
|
580
|
+
type: "object",
|
|
581
|
+
properties: {
|
|
582
|
+
date: { type: "string", description: "Base ISO date (defaults to today)" },
|
|
583
|
+
days: { type: "number", description: "Business days to add/subtract" }
|
|
584
|
+
},
|
|
585
|
+
required: ["days"],
|
|
586
|
+
additionalProperties: false
|
|
587
|
+
},
|
|
588
|
+
tags: ["deterministic", "time", "tool"]
|
|
589
|
+
},
|
|
590
|
+
// LLM-powered skills
|
|
591
|
+
{
|
|
592
|
+
id: "chat",
|
|
593
|
+
name: "Time Chat",
|
|
594
|
+
description: "Conversational assistant for time, date, timezone, and scheduling questions",
|
|
595
|
+
systemPrompt: "You are Timely, a time-focused AI assistant. You help with time, date, timezone, and scheduling questions. Use the available time tools to provide accurate answers. If asked about topics unrelated to time, dates, timezones, or scheduling, politely explain that you specialize in time-related queries and suggest rephrasing the question in a time-related context.",
|
|
596
|
+
tags: ["llm"]
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id: "echo",
|
|
600
|
+
name: "Echo",
|
|
601
|
+
description: "Echoes back the input (for testing)",
|
|
602
|
+
tags: ["testing"]
|
|
603
|
+
}
|
|
604
|
+
];
|
|
605
|
+
var DEFAULT_LLM_CONFIG = {
|
|
606
|
+
provider: "openai",
|
|
607
|
+
model: "alibaba/qwen3.5-flash",
|
|
608
|
+
gatewayModel: "alibaba/qwen3.5-flash",
|
|
609
|
+
temperature: 0.7,
|
|
610
|
+
maxOutputTokens: 4096
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// src/vercel/config.ts
|
|
614
|
+
function resolveConfig(config) {
|
|
615
|
+
return {
|
|
616
|
+
name: config.name,
|
|
617
|
+
description: config.description || `A2A Agent: ${config.name}`,
|
|
618
|
+
version: config.version || "1.0.0",
|
|
619
|
+
skills: config.skills || DEFAULT_SKILLS,
|
|
620
|
+
provider: config.provider || { organization: "Runtype", url: "https://runtype.com" },
|
|
621
|
+
defaultInputModes: config.defaultInputModes || ["text/plain", "application/json"],
|
|
622
|
+
defaultOutputModes: config.defaultOutputModes || ["text/plain", "application/json"],
|
|
623
|
+
echoMode: config.echoMode ?? false,
|
|
624
|
+
llmConfig: config.llmConfig || DEFAULT_LLM_CONFIG,
|
|
625
|
+
agentUrl: config.agentUrl
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function toAgentCardSkills(skills) {
|
|
629
|
+
return skills.map((s) => ({
|
|
630
|
+
id: s.id,
|
|
631
|
+
name: s.name,
|
|
632
|
+
description: s.description,
|
|
633
|
+
tags: s.tags ?? [],
|
|
634
|
+
examples: []
|
|
635
|
+
}));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/vercel/handlers.ts
|
|
639
|
+
var A2A_PROTOCOL_VERSION = "0.3";
|
|
640
|
+
function generateTaskId() {
|
|
641
|
+
return `task_${Date.now()}_${uuidv4().substring(0, 8)}`;
|
|
642
|
+
}
|
|
643
|
+
function jsonRpcSuccess(id, result) {
|
|
644
|
+
return { jsonrpc: "2.0", id, result };
|
|
645
|
+
}
|
|
646
|
+
function jsonRpcError(id, code, message, data) {
|
|
647
|
+
return { jsonrpc: "2.0", id, error: { code, message, data } };
|
|
648
|
+
}
|
|
649
|
+
function jsonResponse(data, status = 200) {
|
|
650
|
+
return new Response(JSON.stringify(data), {
|
|
651
|
+
status,
|
|
652
|
+
headers: {
|
|
653
|
+
"Content-Type": "application/json",
|
|
654
|
+
"Access-Control-Allow-Origin": "*",
|
|
655
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
656
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key"
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
function normalizePart(part) {
|
|
661
|
+
if (part.type) return part;
|
|
662
|
+
if (part.text !== void 0) return { type: "text", text: part.text, mediaType: part.mediaType };
|
|
663
|
+
if (part.data !== void 0) return { type: "data", data: part.data, mediaType: part.mediaType };
|
|
664
|
+
if (part.url !== void 0 || part.raw !== void 0) return { type: "file", uri: part.url ?? part.uri, mimeType: part.mediaType };
|
|
665
|
+
return part;
|
|
666
|
+
}
|
|
667
|
+
function normalizeRole(role) {
|
|
668
|
+
if (role === "ROLE_USER" || role === "user") return "user";
|
|
669
|
+
if (role === "ROLE_AGENT" || role === "agent") return "agent";
|
|
670
|
+
return "user";
|
|
671
|
+
}
|
|
672
|
+
function normalizeMessage(msg) {
|
|
673
|
+
const raw = msg;
|
|
674
|
+
return {
|
|
675
|
+
role: normalizeRole(raw.role || "user"),
|
|
676
|
+
messageId: raw.messageId,
|
|
677
|
+
parts: (raw.parts || []).map(normalizePart),
|
|
678
|
+
contextId: raw.contextId,
|
|
679
|
+
taskId: raw.taskId,
|
|
680
|
+
metadata: raw.metadata
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function extractSendParams(params) {
|
|
684
|
+
const rawMessage = params.message || {};
|
|
685
|
+
const message = normalizeMessage(rawMessage);
|
|
686
|
+
const metadata = params.metadata;
|
|
687
|
+
const skillId = params.skill ?? metadata?.skill;
|
|
688
|
+
const contextId = params.contextId ?? message.contextId;
|
|
689
|
+
return { skillId, message, contextId, metadata };
|
|
690
|
+
}
|
|
691
|
+
function resolveSkill(skillId, skills, echoMode) {
|
|
692
|
+
if (skillId) {
|
|
693
|
+
const found = skills.find((s) => s.id === skillId);
|
|
694
|
+
if (found) return found;
|
|
695
|
+
}
|
|
696
|
+
if (echoMode) return skills.find((s) => s.id === "echo") || skills[0];
|
|
697
|
+
return skills.find((s) => s.id === "chat") || skills[0];
|
|
698
|
+
}
|
|
699
|
+
function toSpecPart(part) {
|
|
700
|
+
const out = {};
|
|
701
|
+
if (part.type === "text" || part.text !== void 0) out.text = part.text ?? "";
|
|
702
|
+
else if (part.type === "data" || part.data !== void 0) out.data = part.data;
|
|
703
|
+
else if (part.type === "file") {
|
|
704
|
+
out.url = part.uri ?? part.url;
|
|
705
|
+
out.mediaType = part.mimeType ?? part.mediaType;
|
|
706
|
+
}
|
|
707
|
+
if (part.mediaType || part.mimeType) out.mediaType = part.mediaType ?? part.mimeType;
|
|
708
|
+
return out;
|
|
709
|
+
}
|
|
710
|
+
function toSpecArtifact(a) {
|
|
711
|
+
return {
|
|
712
|
+
artifactId: a.artifactId ?? uuidv4(),
|
|
713
|
+
name: a.name,
|
|
714
|
+
parts: a.parts.map(toSpecPart)
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function createAgentCardHandler(config) {
|
|
718
|
+
const resolved = resolveConfig(config);
|
|
719
|
+
const agentUrl = resolved.agentUrl || "/api/a2a";
|
|
720
|
+
const agentCard = {
|
|
721
|
+
name: resolved.name,
|
|
722
|
+
description: resolved.description,
|
|
723
|
+
url: agentUrl,
|
|
724
|
+
supportedInterfaces: [
|
|
725
|
+
{
|
|
726
|
+
url: agentUrl,
|
|
727
|
+
protocolBinding: "JSONRPC",
|
|
728
|
+
protocolVersion: A2A_PROTOCOL_VERSION
|
|
729
|
+
}
|
|
730
|
+
],
|
|
731
|
+
version: resolved.version,
|
|
732
|
+
protocolVersion: A2A_PROTOCOL_VERSION,
|
|
733
|
+
defaultInputModes: resolved.defaultInputModes,
|
|
734
|
+
defaultOutputModes: resolved.defaultOutputModes,
|
|
735
|
+
provider: resolved.provider,
|
|
736
|
+
capabilities: {
|
|
737
|
+
streaming: true,
|
|
738
|
+
pushNotifications: false
|
|
739
|
+
},
|
|
740
|
+
skills: toAgentCardSkills(resolved.skills)
|
|
741
|
+
};
|
|
742
|
+
return async function handler(_request) {
|
|
743
|
+
return new Response(JSON.stringify(agentCard), {
|
|
744
|
+
headers: {
|
|
745
|
+
"Content-Type": "application/json",
|
|
746
|
+
"Cache-Control": "public, max-age=3600",
|
|
747
|
+
"Access-Control-Allow-Origin": "*"
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function createA2AHandler(config) {
|
|
753
|
+
const resolved = resolveConfig(config);
|
|
754
|
+
const { skills, llmConfig, echoMode } = resolved;
|
|
755
|
+
return async function handler(request) {
|
|
756
|
+
if (request.method === "OPTIONS") {
|
|
757
|
+
return new Response(null, {
|
|
758
|
+
status: 204,
|
|
759
|
+
headers: {
|
|
760
|
+
"Access-Control-Allow-Origin": "*",
|
|
761
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
762
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key"
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
let body;
|
|
767
|
+
try {
|
|
768
|
+
body = await request.json();
|
|
769
|
+
} catch {
|
|
770
|
+
return jsonResponse(
|
|
771
|
+
jsonRpcError(void 0, JSON_RPC_ERRORS.PARSE_ERROR, "Invalid JSON"),
|
|
772
|
+
400
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
if (body.jsonrpc !== "2.0" || !body.method) {
|
|
776
|
+
return jsonResponse(
|
|
777
|
+
jsonRpcError(body?.id, JSON_RPC_ERRORS.INVALID_REQUEST, "Invalid JSON-RPC request")
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
const { id, method, params } = body;
|
|
781
|
+
console.log(`[A2A] ${method}`, params ? JSON.stringify(params).substring(0, 100) : "");
|
|
782
|
+
try {
|
|
783
|
+
switch (method) {
|
|
784
|
+
case "tasks/send":
|
|
785
|
+
case "message/send":
|
|
786
|
+
case "SendMessage":
|
|
787
|
+
return await handleTasksSend(id, params, skills, llmConfig, echoMode);
|
|
788
|
+
case "tasks/sendSubscribe":
|
|
789
|
+
case "message/stream":
|
|
790
|
+
case "SendStreamingMessage":
|
|
791
|
+
return await handleTasksSendSubscribe(
|
|
792
|
+
id,
|
|
793
|
+
params,
|
|
794
|
+
skills,
|
|
795
|
+
llmConfig,
|
|
796
|
+
echoMode
|
|
797
|
+
);
|
|
798
|
+
case "tasks/get":
|
|
799
|
+
case "GetTask":
|
|
800
|
+
return jsonResponse(
|
|
801
|
+
jsonRpcError(
|
|
802
|
+
id,
|
|
803
|
+
JSON_RPC_ERRORS.TASK_NOT_FOUND,
|
|
804
|
+
"Task state not available in serverless mode. Use message/stream for streaming."
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
case "tasks/cancel":
|
|
808
|
+
case "CancelTask":
|
|
809
|
+
return jsonResponse(
|
|
810
|
+
jsonRpcError(
|
|
811
|
+
id,
|
|
812
|
+
JSON_RPC_ERRORS.TASK_NOT_FOUND,
|
|
813
|
+
"Task cancellation not available in serverless mode."
|
|
814
|
+
)
|
|
815
|
+
);
|
|
816
|
+
case "ping":
|
|
817
|
+
return jsonResponse(jsonRpcSuccess(id, { pong: true }));
|
|
818
|
+
default:
|
|
819
|
+
return jsonResponse(
|
|
820
|
+
jsonRpcError(id, JSON_RPC_ERRORS.METHOD_NOT_FOUND, `Method not found: ${method}`)
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
} catch (error) {
|
|
824
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
825
|
+
console.error(`[A2A] Error in ${method}:`, errorMessage);
|
|
826
|
+
return jsonResponse(jsonRpcError(id, JSON_RPC_ERRORS.INTERNAL_ERROR, errorMessage));
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
async function handleTasksSend(id, params, skills, llmConfig, echoMode) {
|
|
831
|
+
const { skillId, message, contextId, metadata } = extractSendParams(params);
|
|
832
|
+
const skill = resolveSkill(skillId, skills, echoMode);
|
|
833
|
+
const resolvedContextId = contextId || uuidv4();
|
|
834
|
+
const taskId = generateTaskId();
|
|
835
|
+
try {
|
|
836
|
+
const context = {
|
|
837
|
+
taskId,
|
|
838
|
+
contextId: resolvedContextId,
|
|
839
|
+
skill,
|
|
840
|
+
message,
|
|
841
|
+
metadata
|
|
842
|
+
};
|
|
843
|
+
let result;
|
|
844
|
+
if (skill.id.startsWith("time/")) {
|
|
845
|
+
result = await executeTimeSkill(context);
|
|
846
|
+
} else if (echoMode || skill.id === "echo") {
|
|
847
|
+
result = await executeEcho(context);
|
|
848
|
+
} else {
|
|
849
|
+
result = await executeTask(context, llmConfig, skills);
|
|
850
|
+
}
|
|
851
|
+
return jsonResponse(
|
|
852
|
+
jsonRpcSuccess(id, {
|
|
853
|
+
task: {
|
|
854
|
+
id: taskId,
|
|
855
|
+
contextId: resolvedContextId,
|
|
856
|
+
status: { state: "completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
857
|
+
artifacts: (result.artifacts || []).map(toSpecArtifact),
|
|
858
|
+
metadata
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
);
|
|
862
|
+
} catch (error) {
|
|
863
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
864
|
+
return jsonResponse(
|
|
865
|
+
jsonRpcError(id, JSON_RPC_ERRORS.INTERNAL_ERROR, `Task execution failed: ${errorMessage}`)
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async function handleTasksSendSubscribe(id, params, skills, llmConfig, echoMode) {
|
|
870
|
+
const { skillId, message, contextId, metadata } = extractSendParams(params);
|
|
871
|
+
const skill = resolveSkill(skillId, skills, echoMode);
|
|
872
|
+
const resolvedContextId = contextId || uuidv4();
|
|
873
|
+
const taskId = generateTaskId();
|
|
874
|
+
const stream = new ReadableStream({
|
|
875
|
+
async start(controller) {
|
|
876
|
+
const encoder = new TextEncoder();
|
|
877
|
+
const sendStreamResponse = (result) => {
|
|
878
|
+
const rpcResponse = { jsonrpc: "2.0", id, result };
|
|
879
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(rpcResponse)}
|
|
880
|
+
|
|
881
|
+
`));
|
|
882
|
+
};
|
|
883
|
+
sendStreamResponse({
|
|
884
|
+
task: {
|
|
885
|
+
id: taskId,
|
|
886
|
+
contextId: resolvedContextId,
|
|
887
|
+
status: { state: "submitted", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
sendStreamResponse({
|
|
891
|
+
statusUpdate: {
|
|
892
|
+
taskId,
|
|
893
|
+
contextId: resolvedContextId,
|
|
894
|
+
status: { state: "working", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
try {
|
|
898
|
+
const context = {
|
|
899
|
+
taskId,
|
|
900
|
+
contextId: resolvedContextId,
|
|
901
|
+
skill,
|
|
902
|
+
message,
|
|
903
|
+
metadata
|
|
904
|
+
};
|
|
905
|
+
const artifactId = uuidv4();
|
|
906
|
+
let isFirstChunk = true;
|
|
907
|
+
const callbacks = {
|
|
908
|
+
onChunk: async (text) => {
|
|
909
|
+
sendStreamResponse({
|
|
910
|
+
artifactUpdate: {
|
|
911
|
+
taskId,
|
|
912
|
+
contextId: resolvedContextId,
|
|
913
|
+
artifact: { artifactId, name: "response", parts: [{ text }] },
|
|
914
|
+
append: !isFirstChunk,
|
|
915
|
+
lastChunk: false
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
isFirstChunk = false;
|
|
919
|
+
},
|
|
920
|
+
onComplete: async () => {
|
|
921
|
+
sendStreamResponse({
|
|
922
|
+
artifactUpdate: {
|
|
923
|
+
taskId,
|
|
924
|
+
contextId: resolvedContextId,
|
|
925
|
+
artifact: { artifactId, name: "response", parts: [{ text: "" }] },
|
|
926
|
+
append: true,
|
|
927
|
+
lastChunk: true
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
sendStreamResponse({
|
|
931
|
+
statusUpdate: {
|
|
932
|
+
taskId,
|
|
933
|
+
contextId: resolvedContextId,
|
|
934
|
+
status: { state: "completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
controller.close();
|
|
938
|
+
},
|
|
939
|
+
onError: async (error) => {
|
|
940
|
+
sendStreamResponse({
|
|
941
|
+
statusUpdate: {
|
|
942
|
+
taskId,
|
|
943
|
+
contextId: resolvedContextId,
|
|
944
|
+
status: {
|
|
945
|
+
state: "failed",
|
|
946
|
+
message: { role: "agent", parts: [{ text: error.message }] },
|
|
947
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
controller.close();
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
if (skill.id.startsWith("time/")) {
|
|
955
|
+
const timeResult = await executeTimeSkill(context);
|
|
956
|
+
await callbacks.onChunk(timeResult.text);
|
|
957
|
+
await callbacks.onComplete();
|
|
958
|
+
} else if (echoMode || skill.id === "echo") {
|
|
959
|
+
await executeEchoStreaming(context, callbacks);
|
|
960
|
+
} else {
|
|
961
|
+
await executeTaskStreaming(context, llmConfig, callbacks, skills);
|
|
962
|
+
}
|
|
963
|
+
} catch (error) {
|
|
964
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
965
|
+
sendStreamResponse({
|
|
966
|
+
statusUpdate: {
|
|
967
|
+
taskId,
|
|
968
|
+
contextId: resolvedContextId,
|
|
969
|
+
status: {
|
|
970
|
+
state: "failed",
|
|
971
|
+
message: { role: "agent", parts: [{ text: errorMessage }] },
|
|
972
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
controller.close();
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
return new Response(stream, {
|
|
981
|
+
headers: {
|
|
982
|
+
"Content-Type": "text/event-stream",
|
|
983
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
984
|
+
Connection: "keep-alive",
|
|
985
|
+
"X-Accel-Buffering": "no",
|
|
986
|
+
"Access-Control-Allow-Origin": "*"
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function createHandlers(config) {
|
|
991
|
+
return {
|
|
992
|
+
agentCardHandler: createAgentCardHandler(config),
|
|
993
|
+
a2aHandler: createA2AHandler(config)
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
export {
|
|
997
|
+
DEFAULT_LLM_CONFIG,
|
|
998
|
+
DEFAULT_SKILLS,
|
|
999
|
+
createA2AHandler,
|
|
1000
|
+
createAgentCardHandler,
|
|
1001
|
+
createHandlers,
|
|
1002
|
+
resolveConfig,
|
|
1003
|
+
toAgentCardSkills
|
|
1004
|
+
};
|
|
1005
|
+
//# sourceMappingURL=index.js.map
|