@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.
@@ -0,0 +1,1261 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/types.ts
4
+ var JSON_RPC_ERRORS = {
5
+ PARSE_ERROR: -32700,
6
+ INVALID_REQUEST: -32600,
7
+ METHOD_NOT_FOUND: -32601,
8
+ INVALID_PARAMS: -32602,
9
+ INTERNAL_ERROR: -32603,
10
+ // A2A spec error codes
11
+ TASK_NOT_FOUND: -32001,
12
+ TASK_NOT_CANCELABLE: -32002,
13
+ PUSH_NOTIFICATION_NOT_SUPPORTED: -32003,
14
+ UNSUPPORTED_OPERATION: -32004,
15
+ CONTENT_TYPE_NOT_SUPPORTED: -32005,
16
+ INVALID_AGENT_RESPONSE: -32006,
17
+ /** @deprecated Use TASK_NOT_CANCELABLE */
18
+ SKILL_NOT_FOUND: -32002,
19
+ /** @deprecated */
20
+ UNAUTHORIZED: -32003,
21
+ /** @deprecated */
22
+ RATE_LIMITED: -32004,
23
+ /** @deprecated */
24
+ TASK_CANCELED: -32005
25
+ };
26
+
27
+ // src/time-executor.ts
28
+ import {
29
+ format,
30
+ addDays,
31
+ addWeeks,
32
+ addMonths,
33
+ addBusinessDays,
34
+ differenceInDays,
35
+ differenceInHours,
36
+ differenceInMinutes,
37
+ isWeekend,
38
+ isBefore,
39
+ parseISO
40
+ } from "date-fns";
41
+ import { formatInTimeZone } from "date-fns-tz";
42
+ function createResponse(result, source) {
43
+ return {
44
+ result,
45
+ computed: { method: "deterministic", source },
46
+ usage: "Use this value directly. Do not recalculate."
47
+ };
48
+ }
49
+ function extractInput(message) {
50
+ for (const part of message.parts) {
51
+ if ((part.type === "data" || part.data !== void 0) && part.data) {
52
+ return part.data;
53
+ }
54
+ if ((part.type === "text" || part.text !== void 0) && part.text) {
55
+ try {
56
+ return JSON.parse(part.text);
57
+ } catch {
58
+ return { expression: part.text };
59
+ }
60
+ }
61
+ }
62
+ return {};
63
+ }
64
+ async function executeTimeSkill(context) {
65
+ const input = extractInput(context.message);
66
+ let response;
67
+ switch (context.skill.id) {
68
+ case "time/now": {
69
+ const tz = input.timezone || "UTC";
70
+ const now = /* @__PURE__ */ new Date();
71
+ response = createResponse(
72
+ {
73
+ timestamp: now.toISOString(),
74
+ timezone: tz,
75
+ formatted: formatInTimeZone(now, tz, "yyyy-MM-dd'T'HH:mm:ssXXX"),
76
+ human: formatInTimeZone(now, tz, "EEEE, MMMM d, yyyy h:mm a zzz"),
77
+ day: formatInTimeZone(now, tz, "EEEE"),
78
+ offset: formatInTimeZone(now, tz, "xxx")
79
+ },
80
+ "system_clock"
81
+ );
82
+ break;
83
+ }
84
+ case "time/day_of_week": {
85
+ const dateStr = input.date;
86
+ if (!dateStr) throw new Error("Missing required field: date");
87
+ const date = parseISO(dateStr);
88
+ if (isNaN(date.getTime())) throw new Error(`Invalid date: ${dateStr}`);
89
+ response = createResponse(
90
+ {
91
+ date: dateStr,
92
+ day: format(date, "EEEE"),
93
+ // @snake-case-ok: A2A protocol response field
94
+ day_index: date.getDay(),
95
+ // @snake-case-ok: A2A protocol response field
96
+ is_weekend: isWeekend(date)
97
+ },
98
+ "date_arithmetic"
99
+ );
100
+ break;
101
+ }
102
+ case "time/add": {
103
+ const baseStr = input.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
104
+ const base = parseISO(baseStr);
105
+ if (isNaN(base.getTime())) throw new Error(`Invalid date: ${baseStr}`);
106
+ let result = base;
107
+ if (input.days) result = addDays(result, input.days);
108
+ if (input.weeks) result = addWeeks(result, input.weeks);
109
+ if (input.months) result = addMonths(result, input.months);
110
+ response = createResponse(
111
+ {
112
+ original: baseStr,
113
+ computed: format(result, "yyyy-MM-dd"),
114
+ day: format(result, "EEEE"),
115
+ operation: { days: input.days, weeks: input.weeks, months: input.months }
116
+ },
117
+ "date_arithmetic"
118
+ );
119
+ break;
120
+ }
121
+ case "time/business_days": {
122
+ const baseStr = input.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
123
+ const days = input.days;
124
+ if (typeof days !== "number") throw new Error("Missing required field: days");
125
+ const base = parseISO(baseStr);
126
+ if (isNaN(base.getTime())) throw new Error(`Invalid date: ${baseStr}`);
127
+ const result = addBusinessDays(base, days);
128
+ response = createResponse(
129
+ {
130
+ original: baseStr,
131
+ computed: format(result, "yyyy-MM-dd"),
132
+ day: format(result, "EEEE"),
133
+ // @snake-case-ok: A2A protocol response field
134
+ business_days_added: days
135
+ },
136
+ "date_arithmetic"
137
+ );
138
+ break;
139
+ }
140
+ case "time/diff": {
141
+ const from = parseISO(input.from);
142
+ const to = parseISO(input.to);
143
+ if (isNaN(from.getTime())) throw new Error(`Invalid from date: ${input.from}`);
144
+ if (isNaN(to.getTime())) throw new Error(`Invalid to date: ${input.to}`);
145
+ const days = differenceInDays(to, from);
146
+ const hours = differenceInHours(to, from);
147
+ const minutes = differenceInMinutes(to, from);
148
+ response = createResponse(
149
+ {
150
+ from: input.from,
151
+ to: input.to,
152
+ difference: { days, hours, minutes },
153
+ human: `${Math.abs(days)} days`
154
+ },
155
+ "date_arithmetic"
156
+ );
157
+ break;
158
+ }
159
+ case "time/is_past": {
160
+ const ts = input.timestamp;
161
+ if (!ts) throw new Error("Missing required field: timestamp");
162
+ const check = parseISO(ts);
163
+ if (isNaN(check.getTime())) throw new Error(`Invalid timestamp: ${ts}`);
164
+ const now = /* @__PURE__ */ new Date();
165
+ response = createResponse(
166
+ {
167
+ timestamp: ts,
168
+ // @snake-case-ok: A2A protocol response field
169
+ is_past: isBefore(check, now),
170
+ // @snake-case-ok: A2A protocol response field
171
+ is_future: isBefore(now, check),
172
+ // @snake-case-ok: A2A protocol response field
173
+ checked_at: now.toISOString()
174
+ },
175
+ "system_clock"
176
+ );
177
+ break;
178
+ }
179
+ case "time/convert": {
180
+ const ts = input.timestamp;
181
+ const toTz = input.to;
182
+ if (!ts) throw new Error("Missing required field: timestamp");
183
+ if (!toTz) throw new Error("Missing required field: to (timezone)");
184
+ const date = parseISO(ts);
185
+ if (isNaN(date.getTime())) throw new Error(`Invalid timestamp: ${ts}`);
186
+ response = createResponse(
187
+ {
188
+ original: ts,
189
+ converted: formatInTimeZone(date, toTz, "yyyy-MM-dd'T'HH:mm:ssXXX"),
190
+ timezone: toTz,
191
+ human: formatInTimeZone(date, toTz, "EEEE, MMMM d, yyyy h:mm a zzz")
192
+ },
193
+ "date_arithmetic"
194
+ );
195
+ break;
196
+ }
197
+ case "time/parse": {
198
+ const expr = input.expression?.toLowerCase();
199
+ const tz = input.timezone || "UTC";
200
+ const ref = input.reference ? parseISO(input.reference) : /* @__PURE__ */ new Date();
201
+ if (!expr) throw new Error("Missing required field: expression");
202
+ let result = null;
203
+ if (expr === "today") {
204
+ result = ref;
205
+ } else if (expr === "tomorrow") {
206
+ result = addDays(ref, 1);
207
+ } else if (expr === "yesterday") {
208
+ result = addDays(ref, -1);
209
+ } else if (expr.startsWith("next ")) {
210
+ const dayName = expr.replace("next ", "").split(" ")[0];
211
+ const days = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
212
+ const targetDay = days.indexOf(dayName);
213
+ if (targetDay >= 0) {
214
+ let daysToAdd = targetDay - ref.getDay();
215
+ if (daysToAdd <= 0) daysToAdd += 7;
216
+ result = addDays(ref, daysToAdd);
217
+ }
218
+ } else if (expr.includes("in ") && expr.includes(" day")) {
219
+ const match = expr.match(/in (\d+) days?/);
220
+ if (match) result = addDays(ref, parseInt(match[1]));
221
+ } else if (expr.includes("in ") && expr.includes(" week")) {
222
+ const match = expr.match(/in (\d+) weeks?/);
223
+ if (match) result = addWeeks(ref, parseInt(match[1]));
224
+ }
225
+ if (!result) {
226
+ throw new Error(
227
+ `Could not parse expression: "${expr}". Supported: today, tomorrow, yesterday, next [day], in N days/weeks`
228
+ );
229
+ }
230
+ response = createResponse(
231
+ {
232
+ expression: input.expression,
233
+ resolved: format(result, "yyyy-MM-dd"),
234
+ day: format(result, "EEEE"),
235
+ timezone: tz,
236
+ // @snake-case-ok: A2A protocol response field
237
+ reference_used: ref.toISOString()
238
+ },
239
+ "date_arithmetic"
240
+ );
241
+ break;
242
+ }
243
+ default:
244
+ throw new Error(`Unknown time skill: ${context.skill.id}`);
245
+ }
246
+ const text = JSON.stringify(response, null, 2);
247
+ return {
248
+ text,
249
+ artifacts: [
250
+ {
251
+ name: "response",
252
+ parts: [{ type: "text", text }]
253
+ }
254
+ ]
255
+ };
256
+ }
257
+
258
+ // src/executor.ts
259
+ import { streamText, generateText, tool, jsonSchema, stepCountIs } from "ai";
260
+ import { createGateway } from "@ai-sdk/gateway";
261
+ import { createOpenAI } from "@ai-sdk/openai";
262
+ import { createAnthropic } from "@ai-sdk/anthropic";
263
+ var DEFAULT_SYSTEM_PROMPTS = {
264
+ 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.`
265
+ };
266
+ var CHAT_SKILL_ID = "chat";
267
+ var TOOL_STOP_CONDITION = stepCountIs(5);
268
+ var TOOL_TAG = "tool";
269
+ var LLM_TAG = "llm";
270
+ function extractTextFromMessage(message) {
271
+ return message.parts.filter((p) => (p.type === "text" || p.text !== void 0) && p.text).map((p) => p.text).join("\n");
272
+ }
273
+ function createLLMProvider(config) {
274
+ const gatewayKey = process.env.AI_GATEWAY_API_KEY;
275
+ if (gatewayKey) {
276
+ const gateway = createGateway({ apiKey: gatewayKey });
277
+ const modelId = config.gatewayModel || (config.model.includes("/") ? config.model : `${config.provider}/${config.model}`);
278
+ return gateway(modelId);
279
+ }
280
+ if (config.model.includes("/")) {
281
+ throw new Error(
282
+ `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).`
283
+ );
284
+ }
285
+ const apiKey = config.apiKey || process.env[`${config.provider.toUpperCase()}_API_KEY`];
286
+ if (!apiKey) {
287
+ throw new Error(
288
+ `API key not provided. Set AI_GATEWAY_API_KEY for Vercel AI Gateway, or ${config.provider.toUpperCase()}_API_KEY for direct provider access.`
289
+ );
290
+ }
291
+ switch (config.provider) {
292
+ case "openai":
293
+ return createOpenAI({ apiKey })(config.model);
294
+ case "anthropic":
295
+ return createAnthropic({ apiKey })(config.model);
296
+ default:
297
+ throw new Error(`Unsupported LLM provider: ${config.provider}`);
298
+ }
299
+ }
300
+ function getSystemPrompt(skill) {
301
+ if (skill.systemPrompt) {
302
+ return skill.systemPrompt;
303
+ }
304
+ return DEFAULT_SYSTEM_PROMPTS[skill.id] || DEFAULT_SYSTEM_PROMPTS.chat;
305
+ }
306
+ function sanitizeToolName(skillId) {
307
+ let name = skillId.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
308
+ if (!name) {
309
+ name = "tool";
310
+ }
311
+ if (/^[0-9]/.test(name)) {
312
+ name = `tool_${name}`;
313
+ }
314
+ return name;
315
+ }
316
+ function makeUniqueToolName(skillId, usedNames) {
317
+ const baseName = sanitizeToolName(skillId);
318
+ if (!usedNames.has(baseName)) {
319
+ usedNames.add(baseName);
320
+ return baseName;
321
+ }
322
+ let counter = 1;
323
+ let candidate = `${baseName}_${counter}`;
324
+ while (usedNames.has(candidate)) {
325
+ counter += 1;
326
+ candidate = `${baseName}_${counter}`;
327
+ }
328
+ usedNames.add(candidate);
329
+ return candidate;
330
+ }
331
+ function toToolInput(params) {
332
+ if (params && typeof params === "object" && !Array.isArray(params)) {
333
+ return params;
334
+ }
335
+ return { input: params };
336
+ }
337
+ function parseToolResult(text) {
338
+ try {
339
+ return JSON.parse(text);
340
+ } catch {
341
+ return { text };
342
+ }
343
+ }
344
+ async function executeCatalogToolSkill(parentContext, skill, params) {
345
+ if (skill.id.startsWith("time/")) {
346
+ const toolContext = {
347
+ taskId: `${parentContext.taskId}_tool_${skill.id}`,
348
+ contextId: parentContext.contextId,
349
+ skill,
350
+ message: {
351
+ role: "user",
352
+ parts: [{ type: "data", data: toToolInput(params) }]
353
+ },
354
+ metadata: parentContext.metadata
355
+ };
356
+ const result = await executeTimeSkill(toolContext);
357
+ return parseToolResult(result.text);
358
+ }
359
+ throw new Error(`Unsupported tool skill: ${skill.id}`);
360
+ }
361
+ function buildChatTools(context, skillsCatalog) {
362
+ if (context.skill.id !== CHAT_SKILL_ID || !skillsCatalog || skillsCatalog.length === 0) {
363
+ return {};
364
+ }
365
+ const tools = {};
366
+ const usedNames = /* @__PURE__ */ new Set();
367
+ for (const skill of skillsCatalog) {
368
+ if (skill.id === context.skill.id) {
369
+ continue;
370
+ }
371
+ const tags = skill.tags ?? [];
372
+ if (!tags.includes(TOOL_TAG) || tags.includes(LLM_TAG)) {
373
+ continue;
374
+ }
375
+ if (!skill.id.startsWith("time/")) {
376
+ continue;
377
+ }
378
+ const toolName = makeUniqueToolName(skill.id, usedNames);
379
+ const schema = skill.inputSchema ?? {
380
+ type: "object",
381
+ properties: {},
382
+ additionalProperties: true
383
+ };
384
+ tools[toolName] = tool({
385
+ description: skill.description || `Execute ${skill.name}`,
386
+ inputSchema: jsonSchema(schema),
387
+ execute: async (params) => executeCatalogToolSkill(context, skill, params)
388
+ });
389
+ }
390
+ return tools;
391
+ }
392
+ async function executeTask(context, llmConfig, skillsCatalog) {
393
+ const model = createLLMProvider(llmConfig);
394
+ const userMessage = extractTextFromMessage(context.message);
395
+ const systemPrompt = getSystemPrompt(context.skill);
396
+ const chatTools = buildChatTools(context, skillsCatalog);
397
+ const hasChatTools = Object.keys(chatTools).length > 0;
398
+ const result = await generateText({
399
+ model,
400
+ system: systemPrompt,
401
+ prompt: userMessage,
402
+ temperature: llmConfig.temperature ?? 0.7,
403
+ maxOutputTokens: llmConfig.maxOutputTokens ?? 4096,
404
+ ...hasChatTools ? { tools: chatTools, stopWhen: TOOL_STOP_CONDITION } : {}
405
+ });
406
+ return {
407
+ text: result.text,
408
+ artifacts: [
409
+ {
410
+ name: "response",
411
+ parts: [{ type: "text", text: result.text }]
412
+ }
413
+ ]
414
+ };
415
+ }
416
+ async function executeTaskStreaming(context, llmConfig, callbacks, skillsCatalog) {
417
+ const model = createLLMProvider(llmConfig);
418
+ const userMessage = extractTextFromMessage(context.message);
419
+ const systemPrompt = getSystemPrompt(context.skill);
420
+ const chatTools = buildChatTools(context, skillsCatalog);
421
+ const hasChatTools = Object.keys(chatTools).length > 0;
422
+ const result = streamText({
423
+ model,
424
+ system: systemPrompt,
425
+ prompt: userMessage,
426
+ temperature: llmConfig.temperature ?? 0.7,
427
+ maxOutputTokens: llmConfig.maxOutputTokens ?? 4096,
428
+ ...hasChatTools ? { tools: chatTools, stopWhen: TOOL_STOP_CONDITION } : {},
429
+ onError: async ({ error }) => {
430
+ await callbacks.onError(error instanceof Error ? error : new Error(String(error)));
431
+ }
432
+ });
433
+ for await (const chunk of result.textStream) {
434
+ await callbacks.onChunk(chunk);
435
+ }
436
+ await callbacks.onComplete();
437
+ }
438
+ async function executeEcho(context) {
439
+ const userMessage = extractTextFromMessage(context.message);
440
+ return {
441
+ text: `Echo from ${context.skill.name}: ${userMessage}`,
442
+ artifacts: [
443
+ {
444
+ name: "response",
445
+ parts: [{ type: "text", text: `Echo from ${context.skill.name}: ${userMessage}` }]
446
+ }
447
+ ]
448
+ };
449
+ }
450
+ async function executeEchoStreaming(context, callbacks) {
451
+ const userMessage = extractTextFromMessage(context.message);
452
+ const response = `Echo from ${context.skill.name}: ${userMessage}`;
453
+ try {
454
+ const words = response.split(" ");
455
+ for (let i = 0; i < words.length; i++) {
456
+ const chunk = (i === 0 ? "" : " ") + words[i];
457
+ await callbacks.onChunk(chunk);
458
+ await new Promise((resolve) => setTimeout(resolve, 50));
459
+ }
460
+ await callbacks.onComplete();
461
+ } catch (error) {
462
+ await callbacks.onError(error instanceof Error ? error : new Error(String(error)));
463
+ }
464
+ }
465
+
466
+ // src/default-skills.ts
467
+ var DEFAULT_SKILLS = [
468
+ // Time tools (deterministic — no LLM)
469
+ {
470
+ id: "time/now",
471
+ name: "Current Time",
472
+ description: "Returns current UTC time with optional timezone conversion",
473
+ inputSchema: {
474
+ type: "object",
475
+ properties: {
476
+ timezone: { type: "string", description: "IANA timezone name (e.g. America/New_York)" }
477
+ },
478
+ additionalProperties: false
479
+ },
480
+ tags: ["deterministic", "time", "tool"]
481
+ },
482
+ {
483
+ id: "time/parse",
484
+ name: "Parse Date Expression",
485
+ description: 'Converts "next Tuesday at 3pm" to ISO timestamp',
486
+ inputSchema: {
487
+ type: "object",
488
+ properties: {
489
+ expression: { type: "string", description: "Natural language date expression" },
490
+ timezone: { type: "string", description: "IANA timezone name (default: UTC)" },
491
+ reference: { type: "string", description: "Reference ISO timestamp", format: "date-time" }
492
+ },
493
+ required: ["expression"],
494
+ additionalProperties: false
495
+ },
496
+ tags: ["deterministic", "time", "tool"]
497
+ },
498
+ {
499
+ id: "time/convert",
500
+ name: "Convert Timezone",
501
+ description: "Converts a timestamp between timezones",
502
+ inputSchema: {
503
+ type: "object",
504
+ properties: {
505
+ timestamp: { type: "string", description: "ISO timestamp to convert", format: "date-time" },
506
+ to: { type: "string", description: "Target IANA timezone name" }
507
+ },
508
+ required: ["timestamp", "to"],
509
+ additionalProperties: false
510
+ },
511
+ tags: ["deterministic", "time", "tool"]
512
+ },
513
+ {
514
+ id: "time/add",
515
+ name: "Date Arithmetic",
516
+ description: "Add days, weeks, or months to a date",
517
+ inputSchema: {
518
+ type: "object",
519
+ properties: {
520
+ date: { type: "string", description: "Base ISO date (defaults to today)" },
521
+ days: { type: "number", description: "Days to add (can be negative)" },
522
+ weeks: { type: "number", description: "Weeks to add (can be negative)" },
523
+ months: { type: "number", description: "Months to add (can be negative)" }
524
+ },
525
+ additionalProperties: false
526
+ },
527
+ tags: ["deterministic", "time", "tool"]
528
+ },
529
+ {
530
+ id: "time/diff",
531
+ name: "Date Difference",
532
+ description: "Calculate duration between two dates",
533
+ inputSchema: {
534
+ type: "object",
535
+ properties: {
536
+ from: { type: "string", description: "Start ISO timestamp", format: "date-time" },
537
+ to: { type: "string", description: "End ISO timestamp", format: "date-time" }
538
+ },
539
+ required: ["from", "to"],
540
+ additionalProperties: false
541
+ },
542
+ tags: ["deterministic", "time", "tool"]
543
+ },
544
+ {
545
+ id: "time/day_of_week",
546
+ name: "Day of Week",
547
+ description: "Returns the day of the week for a given date",
548
+ inputSchema: {
549
+ type: "object",
550
+ properties: {
551
+ date: { type: "string", description: "ISO date string" }
552
+ },
553
+ required: ["date"],
554
+ additionalProperties: false
555
+ },
556
+ tags: ["deterministic", "time", "tool"]
557
+ },
558
+ {
559
+ id: "time/is_past",
560
+ name: "Is Past",
561
+ description: "Checks whether a timestamp is in the past",
562
+ inputSchema: {
563
+ type: "object",
564
+ properties: {
565
+ timestamp: { type: "string", description: "ISO timestamp to check", format: "date-time" }
566
+ },
567
+ required: ["timestamp"],
568
+ additionalProperties: false
569
+ },
570
+ tags: ["deterministic", "time", "tool"]
571
+ },
572
+ {
573
+ id: "time/business_days",
574
+ name: "Business Days",
575
+ description: "Add or subtract business days from a date",
576
+ inputSchema: {
577
+ type: "object",
578
+ properties: {
579
+ date: { type: "string", description: "Base ISO date (defaults to today)" },
580
+ days: { type: "number", description: "Business days to add/subtract" }
581
+ },
582
+ required: ["days"],
583
+ additionalProperties: false
584
+ },
585
+ tags: ["deterministic", "time", "tool"]
586
+ },
587
+ // LLM-powered skills
588
+ {
589
+ id: "chat",
590
+ name: "Time Chat",
591
+ description: "Conversational assistant for time, date, timezone, and scheduling questions",
592
+ 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.",
593
+ tags: ["llm"]
594
+ },
595
+ {
596
+ id: "echo",
597
+ name: "Echo",
598
+ description: "Echoes back the input (for testing)",
599
+ tags: ["testing"]
600
+ }
601
+ ];
602
+ var DEFAULT_LLM_CONFIG = {
603
+ provider: "openai",
604
+ model: "alibaba/qwen3.5-flash",
605
+ gatewayModel: "alibaba/qwen3.5-flash",
606
+ temperature: 0.7,
607
+ maxOutputTokens: 4096
608
+ };
609
+
610
+ // src/server.ts
611
+ import express from "express";
612
+ import { v4 as uuidv4 } from "uuid";
613
+ var A2A_PROTOCOL_VERSION = "0.3";
614
+ function normalizePart(part) {
615
+ if (part.type) return part;
616
+ if (part.text !== void 0) return { type: "text", text: part.text, mediaType: part.mediaType };
617
+ if (part.data !== void 0) return { type: "data", data: part.data, mediaType: part.mediaType };
618
+ if (part.url !== void 0 || part.raw !== void 0) return { type: "file", uri: part.url ?? part.uri, mimeType: part.mediaType };
619
+ return part;
620
+ }
621
+ function normalizeRole(role) {
622
+ if (role === "ROLE_USER" || role === "user") return "user";
623
+ if (role === "ROLE_AGENT" || role === "agent") return "agent";
624
+ return "user";
625
+ }
626
+ function normalizeMessage(msg) {
627
+ const raw = msg;
628
+ return {
629
+ role: normalizeRole(raw.role || "user"),
630
+ messageId: raw.messageId,
631
+ parts: (raw.parts || []).map(normalizePart),
632
+ contextId: raw.contextId,
633
+ taskId: raw.taskId,
634
+ metadata: raw.metadata
635
+ };
636
+ }
637
+ function extractSendParams(params) {
638
+ const rawMessage = params.message || {};
639
+ const message = normalizeMessage(rawMessage);
640
+ const metadata = params.metadata;
641
+ const skillId = params.skill ?? metadata?.skill;
642
+ const contextId = params.contextId ?? message.contextId;
643
+ return { skillId, message, contextId, metadata };
644
+ }
645
+ function toSpecPart(part) {
646
+ const out = {};
647
+ if (part.type === "text" || part.text !== void 0) out.text = part.text ?? "";
648
+ else if (part.type === "data" || part.data !== void 0) out.data = part.data;
649
+ else if (part.type === "file") {
650
+ out.url = part.uri ?? part.url;
651
+ out.mediaType = part.mimeType ?? part.mediaType;
652
+ }
653
+ if (part.mediaType || part.mimeType) out.mediaType = part.mediaType ?? part.mimeType;
654
+ if (part.metadata) out.metadata = part.metadata;
655
+ return out;
656
+ }
657
+ function toSpecArtifact(a) {
658
+ return {
659
+ artifactId: a.artifactId ?? uuidv4(),
660
+ name: a.name,
661
+ parts: a.parts.map(toSpecPart)
662
+ };
663
+ }
664
+ var tasks = /* @__PURE__ */ new Map();
665
+ function generateTaskId() {
666
+ return `task_${Date.now()}_${uuidv4().substring(0, 8)}`;
667
+ }
668
+ function jsonRpcSuccess(id, result) {
669
+ return { jsonrpc: "2.0", id, result };
670
+ }
671
+ function jsonRpcError(id, code, message, data) {
672
+ return { jsonrpc: "2.0", id, error: { code, message, data } };
673
+ }
674
+ function createA2AServer(options) {
675
+ const { config, llmConfig = DEFAULT_LLM_CONFIG, echoMode = false } = options;
676
+ const app = express();
677
+ app.use(express.json());
678
+ app.use((req, res, next) => {
679
+ res.header("Access-Control-Allow-Origin", "*");
680
+ res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
681
+ res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key");
682
+ if (req.method === "OPTIONS") {
683
+ res.sendStatus(204);
684
+ return;
685
+ }
686
+ next();
687
+ });
688
+ const port = config.port || 9999;
689
+ const host = config.host || "localhost";
690
+ const skills = config.skills || DEFAULT_SKILLS;
691
+ const agentUrl = `http://${host}:${port}/a2a`;
692
+ const agentCard = {
693
+ name: config.name,
694
+ description: config.description || `A2A Agent: ${config.name}`,
695
+ url: agentUrl,
696
+ supportedInterfaces: [
697
+ {
698
+ url: agentUrl,
699
+ protocolBinding: "JSONRPC",
700
+ protocolVersion: A2A_PROTOCOL_VERSION
701
+ }
702
+ ],
703
+ version: config.version || "1.0.0",
704
+ protocolVersion: A2A_PROTOCOL_VERSION,
705
+ defaultInputModes: config.defaultInputModes || ["text/plain", "application/json"],
706
+ defaultOutputModes: config.defaultOutputModes || ["text/plain", "application/json"],
707
+ provider: config.provider || {
708
+ organization: "Runtype",
709
+ url: "https://runtype.com"
710
+ },
711
+ capabilities: {
712
+ streaming: true,
713
+ pushNotifications: false
714
+ },
715
+ skills: skills.map((s) => ({
716
+ id: s.id,
717
+ name: s.name,
718
+ description: s.description,
719
+ tags: s.tags ?? [],
720
+ examples: []
721
+ }))
722
+ };
723
+ const agentCardHandler = (_req, res) => {
724
+ res.setHeader("Cache-Control", "public, max-age=3600");
725
+ res.json(agentCard);
726
+ };
727
+ app.get("/.well-known/agent.json", agentCardHandler);
728
+ app.get("/.well-known/agent-card.json", agentCardHandler);
729
+ app.get("/health", (_req, res) => {
730
+ res.json({ status: "healthy", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
731
+ });
732
+ app.post("/a2a", async (req, res) => {
733
+ const body = req.body;
734
+ if (body.jsonrpc !== "2.0" || !body.method) {
735
+ res.json(jsonRpcError(body?.id, JSON_RPC_ERRORS.INVALID_REQUEST, "Invalid JSON-RPC request"));
736
+ return;
737
+ }
738
+ const { id, method, params } = body;
739
+ console.log(`[A2A] ${method}`, params ? JSON.stringify(params).substring(0, 100) : "");
740
+ try {
741
+ switch (method) {
742
+ case "tasks/send":
743
+ case "message/send":
744
+ case "SendMessage":
745
+ await handleTasksSend(req, res, id, params, skills, llmConfig, echoMode);
746
+ break;
747
+ case "tasks/sendSubscribe":
748
+ case "message/stream":
749
+ case "SendStreamingMessage":
750
+ await handleTasksSendSubscribe(req, res, id, params, skills, llmConfig, echoMode);
751
+ break;
752
+ case "tasks/get":
753
+ case "GetTask":
754
+ handleTasksGet(res, id, params);
755
+ break;
756
+ case "tasks/cancel":
757
+ case "CancelTask":
758
+ handleTasksCancel(res, id, params);
759
+ break;
760
+ case "tasks/resubscribe":
761
+ case "SubscribeToTask":
762
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.UNSUPPORTED_OPERATION, "SubscribeToTask is not supported"));
763
+ break;
764
+ case "ping":
765
+ res.json(jsonRpcSuccess(id, { pong: true }));
766
+ break;
767
+ default:
768
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.METHOD_NOT_FOUND, `Method not found: ${method}`));
769
+ }
770
+ } catch (error) {
771
+ const errorMessage = error instanceof Error ? error.message : String(error);
772
+ console.error(`[A2A] Error in ${method}:`, errorMessage);
773
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.INTERNAL_ERROR, errorMessage));
774
+ }
775
+ });
776
+ let server = null;
777
+ return {
778
+ app,
779
+ start: () => new Promise((resolve) => {
780
+ server = app.listen(port, () => {
781
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
782
+ console.log(`A2A Agent Server: ${config.name}`);
783
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
784
+ console.log("");
785
+ console.log(` Agent Card: http://${host}:${port}/.well-known/agent-card.json`);
786
+ console.log(` A2A Endpoint: http://${host}:${port}/a2a`);
787
+ console.log(` Health Check: http://${host}:${port}/health`);
788
+ console.log("");
789
+ console.log(" Skills:");
790
+ for (const skill of skills) {
791
+ console.log(` - ${skill.id}: ${skill.description}`);
792
+ }
793
+ console.log("");
794
+ console.log(` Mode: ${echoMode ? "Echo (testing)" : `LLM (${llmConfig.model.includes("/") ? llmConfig.model : `${llmConfig.provider}/${llmConfig.model}`})`}`);
795
+ console.log("");
796
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
797
+ console.log("");
798
+ resolve();
799
+ });
800
+ }),
801
+ stop: () => new Promise((resolve, reject) => {
802
+ if (server) {
803
+ server.close((err) => {
804
+ if (err) reject(err);
805
+ else resolve();
806
+ });
807
+ } else {
808
+ resolve();
809
+ }
810
+ })
811
+ };
812
+ }
813
+ function resolveSkill(skillId, skills, echoMode) {
814
+ if (skillId) {
815
+ const found = skills.find((s) => s.id === skillId);
816
+ if (found) return found;
817
+ }
818
+ if (echoMode) return skills.find((s) => s.id === "echo") || skills[0];
819
+ return skills.find((s) => s.id === "chat") || skills[0];
820
+ }
821
+ async function handleTasksSend(_req, res, id, params, skills, llmConfig, echoMode) {
822
+ const { skillId, message, contextId, metadata } = extractSendParams(params);
823
+ const skill = resolveSkill(skillId, skills, echoMode);
824
+ const resolvedContextId = contextId || uuidv4();
825
+ const taskId = generateTaskId();
826
+ const task = {
827
+ id: taskId,
828
+ contextId: resolvedContextId,
829
+ status: "submitted",
830
+ skillId: skill.id,
831
+ requestMessage: message,
832
+ artifacts: [],
833
+ metadata,
834
+ history: [{ status: "submitted", message: "Task created", timestamp: (/* @__PURE__ */ new Date()).toISOString() }]
835
+ };
836
+ tasks.set(taskId, task);
837
+ task.status = "working";
838
+ task.history.push({ status: "working", message: "Execution started", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
839
+ try {
840
+ const context = {
841
+ taskId,
842
+ contextId: resolvedContextId,
843
+ skill,
844
+ message,
845
+ metadata
846
+ };
847
+ let result;
848
+ if (skill.id.startsWith("time/")) {
849
+ result = await executeTimeSkill(context);
850
+ } else if (echoMode || skill.id === "echo") {
851
+ result = await executeEcho(context);
852
+ } else {
853
+ result = await executeTask(context, llmConfig, skills);
854
+ }
855
+ task.status = "completed";
856
+ task.artifacts = result.artifacts || [];
857
+ task.history.push({ status: "completed", message: "Task completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
858
+ res.json(
859
+ jsonRpcSuccess(id, {
860
+ task: {
861
+ id: taskId,
862
+ contextId: resolvedContextId,
863
+ status: { state: "completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
864
+ artifacts: task.artifacts.map(toSpecArtifact),
865
+ metadata
866
+ }
867
+ })
868
+ );
869
+ } catch (error) {
870
+ const errorMessage = error instanceof Error ? error.message : String(error);
871
+ task.status = "failed";
872
+ task.error = { message: errorMessage };
873
+ task.history.push({ status: "failed", message: errorMessage, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
874
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.INTERNAL_ERROR, `Task execution failed: ${errorMessage}`));
875
+ }
876
+ }
877
+ async function handleTasksSendSubscribe(_req, res, id, params, skills, llmConfig, echoMode) {
878
+ const { skillId, message, contextId, metadata } = extractSendParams(params);
879
+ const skill = resolveSkill(skillId, skills, echoMode);
880
+ const resolvedContextId = contextId || uuidv4();
881
+ const taskId = generateTaskId();
882
+ const task = {
883
+ id: taskId,
884
+ contextId: resolvedContextId,
885
+ status: "submitted",
886
+ skillId: skill.id,
887
+ requestMessage: message,
888
+ artifacts: [],
889
+ metadata,
890
+ history: [{ status: "submitted", message: "Task created", timestamp: (/* @__PURE__ */ new Date()).toISOString() }]
891
+ };
892
+ tasks.set(taskId, task);
893
+ res.setHeader("Content-Type", "text/event-stream");
894
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
895
+ res.setHeader("Connection", "keep-alive");
896
+ res.setHeader("X-Accel-Buffering", "no");
897
+ const sendStreamResponse = (result) => {
898
+ const rpcResponse = { jsonrpc: "2.0", id, result };
899
+ res.write(`data: ${JSON.stringify(rpcResponse)}
900
+
901
+ `);
902
+ };
903
+ sendStreamResponse({
904
+ task: {
905
+ id: taskId,
906
+ contextId: resolvedContextId,
907
+ status: { state: "submitted", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
908
+ }
909
+ });
910
+ task.status = "working";
911
+ task.history.push({ status: "working", message: "Execution started", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
912
+ sendStreamResponse({
913
+ statusUpdate: {
914
+ taskId,
915
+ contextId: resolvedContextId,
916
+ status: { state: "working", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
917
+ }
918
+ });
919
+ try {
920
+ const context = {
921
+ taskId,
922
+ contextId: resolvedContextId,
923
+ skill,
924
+ message,
925
+ metadata
926
+ };
927
+ const artifactId = uuidv4();
928
+ let isFirstChunk = true;
929
+ let fullText = "";
930
+ const callbacks = {
931
+ onChunk: async (text) => {
932
+ fullText += text;
933
+ sendStreamResponse({
934
+ artifactUpdate: {
935
+ taskId,
936
+ contextId: resolvedContextId,
937
+ artifact: {
938
+ artifactId,
939
+ name: "response",
940
+ parts: [{ text }]
941
+ },
942
+ append: !isFirstChunk,
943
+ lastChunk: false
944
+ }
945
+ });
946
+ isFirstChunk = false;
947
+ },
948
+ onComplete: async () => {
949
+ sendStreamResponse({
950
+ artifactUpdate: {
951
+ taskId,
952
+ contextId: resolvedContextId,
953
+ artifact: {
954
+ artifactId,
955
+ name: "response",
956
+ parts: [{ text: "" }]
957
+ },
958
+ append: true,
959
+ lastChunk: true
960
+ }
961
+ });
962
+ task.status = "completed";
963
+ task.artifacts = [{ artifactId, name: "response", parts: [{ type: "text", text: fullText }] }];
964
+ task.history.push({ status: "completed", message: "Task completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
965
+ sendStreamResponse({
966
+ statusUpdate: {
967
+ taskId,
968
+ contextId: resolvedContextId,
969
+ status: { state: "completed", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
970
+ }
971
+ });
972
+ res.end();
973
+ },
974
+ onError: async (error) => {
975
+ task.status = "failed";
976
+ task.error = { message: error.message };
977
+ task.history.push({ status: "failed", message: error.message, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
978
+ sendStreamResponse({
979
+ statusUpdate: {
980
+ taskId,
981
+ contextId: resolvedContextId,
982
+ status: {
983
+ state: "failed",
984
+ message: { role: "agent", parts: [{ text: error.message }] },
985
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
986
+ }
987
+ }
988
+ });
989
+ res.end();
990
+ }
991
+ };
992
+ if (skill.id.startsWith("time/")) {
993
+ const timeResult = await executeTimeSkill(context);
994
+ await callbacks.onChunk(timeResult.text);
995
+ await callbacks.onComplete();
996
+ } else if (echoMode || skill.id === "echo") {
997
+ await executeEchoStreaming(context, callbacks);
998
+ } else {
999
+ await executeTaskStreaming(context, llmConfig, callbacks, skills);
1000
+ }
1001
+ } catch (error) {
1002
+ const errorMessage = error instanceof Error ? error.message : String(error);
1003
+ task.status = "failed";
1004
+ task.error = { message: errorMessage };
1005
+ task.history.push({ status: "failed", message: errorMessage, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
1006
+ sendStreamResponse({
1007
+ statusUpdate: {
1008
+ taskId,
1009
+ contextId: resolvedContextId,
1010
+ status: {
1011
+ state: "failed",
1012
+ message: { role: "agent", parts: [{ text: errorMessage }] },
1013
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1014
+ }
1015
+ }
1016
+ });
1017
+ res.end();
1018
+ }
1019
+ }
1020
+ function handleTasksGet(res, id, params) {
1021
+ const resolvedTaskId = params.id || params.taskId;
1022
+ if (!resolvedTaskId) {
1023
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.INVALID_PARAMS, "Missing task id"));
1024
+ return;
1025
+ }
1026
+ const task = tasks.get(resolvedTaskId);
1027
+ if (!task) {
1028
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.TASK_NOT_FOUND, `Task not found: ${resolvedTaskId}`));
1029
+ return;
1030
+ }
1031
+ const result = {
1032
+ task: {
1033
+ id: task.id,
1034
+ contextId: task.contextId,
1035
+ status: { state: task.status, timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1036
+ artifacts: task.artifacts.map(toSpecArtifact),
1037
+ metadata: task.metadata
1038
+ }
1039
+ };
1040
+ res.json(jsonRpcSuccess(id, result));
1041
+ }
1042
+ function handleTasksCancel(res, id, params) {
1043
+ const resolvedTaskId = params.id || params.taskId;
1044
+ if (!resolvedTaskId) {
1045
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.INVALID_PARAMS, "Missing task id"));
1046
+ return;
1047
+ }
1048
+ const task = tasks.get(resolvedTaskId);
1049
+ if (!task) {
1050
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.TASK_NOT_FOUND, `Task not found: ${resolvedTaskId}`));
1051
+ return;
1052
+ }
1053
+ if (["completed", "failed", "canceled"].includes(task.status)) {
1054
+ res.json(jsonRpcError(id, JSON_RPC_ERRORS.TASK_NOT_CANCELABLE, `Task cannot be canceled: already ${task.status}`));
1055
+ return;
1056
+ }
1057
+ task.status = "canceled";
1058
+ task.history.push({
1059
+ status: "canceled",
1060
+ message: params.reason || "Canceled by request",
1061
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1062
+ });
1063
+ res.json(jsonRpcSuccess(id, {
1064
+ task: {
1065
+ id: resolvedTaskId,
1066
+ contextId: task.contextId,
1067
+ status: { state: "canceled", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
1068
+ }
1069
+ }));
1070
+ }
1071
+
1072
+ // src/client.ts
1073
+ var A2AClient = class {
1074
+ baseUrl;
1075
+ rpcUrl;
1076
+ agentCardUrl;
1077
+ apiKey;
1078
+ headers;
1079
+ constructor(options) {
1080
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
1081
+ const isA2aBase = this.baseUrl.endsWith("/a2a");
1082
+ this.rpcUrl = isA2aBase ? this.baseUrl : `${this.baseUrl}/a2a`;
1083
+ this.agentCardUrl = `${this.baseUrl}/.well-known/agent-card.json`;
1084
+ this.apiKey = options.apiKey;
1085
+ this.headers = options.headers || {};
1086
+ }
1087
+ async getAgentCard() {
1088
+ const response = await fetch(this.agentCardUrl, {
1089
+ headers: this.getHeaders()
1090
+ });
1091
+ if (!response.ok) {
1092
+ throw new Error(`Failed to fetch agent card: ${response.status} ${response.statusText}`);
1093
+ }
1094
+ return response.json();
1095
+ }
1096
+ async sendTask(params) {
1097
+ const request = {
1098
+ jsonrpc: "2.0",
1099
+ id: `req_${Date.now()}`,
1100
+ method: "message/send",
1101
+ params: this.toSendMessageRequest(params)
1102
+ };
1103
+ const response = await this.sendJsonRpc(request);
1104
+ if (response.error) {
1105
+ throw new Error(`A2A error: ${response.error.message} (code: ${response.error.code})`);
1106
+ }
1107
+ return response.result.task;
1108
+ }
1109
+ async sendTaskStreaming(params, callbacks) {
1110
+ const request = {
1111
+ jsonrpc: "2.0",
1112
+ id: `req_${Date.now()}`,
1113
+ method: "message/stream",
1114
+ params: this.toSendMessageRequest(params)
1115
+ };
1116
+ const response = await fetch(this.rpcUrl, {
1117
+ method: "POST",
1118
+ headers: {
1119
+ "Content-Type": "application/json",
1120
+ ...this.getHeaders()
1121
+ },
1122
+ body: JSON.stringify(request)
1123
+ });
1124
+ if (!response.ok) {
1125
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1126
+ }
1127
+ const reader = response.body?.getReader();
1128
+ if (!reader) {
1129
+ throw new Error("No response body");
1130
+ }
1131
+ const decoder = new TextDecoder();
1132
+ let buffer = "";
1133
+ while (true) {
1134
+ const { done, value } = await reader.read();
1135
+ if (done) break;
1136
+ buffer += decoder.decode(value, { stream: true });
1137
+ const lines = buffer.split("\n");
1138
+ buffer = lines.pop() || "";
1139
+ for (const line of lines) {
1140
+ if (!line.startsWith("data: ")) continue;
1141
+ const payload = JSON.parse(line.substring(6));
1142
+ const result = payload.result || payload;
1143
+ if (result.statusUpdate) {
1144
+ const state = result.statusUpdate.status?.state || result.statusUpdate.status;
1145
+ callbacks.onStatus?.(state, result.statusUpdate);
1146
+ } else if (result.artifactUpdate) {
1147
+ const artifact = result.artifactUpdate.artifact;
1148
+ callbacks.onArtifact?.(artifact);
1149
+ if (artifact?.parts) {
1150
+ for (const part of artifact.parts) {
1151
+ const text = part.text ?? (part.type === "text" ? part.text : void 0);
1152
+ if (text) callbacks.onChunk?.(text);
1153
+ }
1154
+ }
1155
+ } else if (result.task) {
1156
+ const state = result.task.status?.state || result.task.status;
1157
+ if (state) callbacks.onStatus?.(state, result.task);
1158
+ } else if (result.error || payload.error) {
1159
+ callbacks.onError?.(result.error || payload.error);
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+ async getTask(taskId, historyLength) {
1165
+ const request = {
1166
+ jsonrpc: "2.0",
1167
+ id: `req_${Date.now()}`,
1168
+ method: "GetTask",
1169
+ params: { id: taskId, historyLength }
1170
+ };
1171
+ const response = await this.sendJsonRpc(request);
1172
+ if (response.error) {
1173
+ throw new Error(`A2A error: ${response.error.message} (code: ${response.error.code})`);
1174
+ }
1175
+ return response.result.task;
1176
+ }
1177
+ async cancelTask(taskId, reason) {
1178
+ const request = {
1179
+ jsonrpc: "2.0",
1180
+ id: `req_${Date.now()}`,
1181
+ method: "CancelTask",
1182
+ params: { id: taskId, metadata: reason ? { reason } : void 0 }
1183
+ };
1184
+ const response = await this.sendJsonRpc(request);
1185
+ if (response.error) {
1186
+ throw new Error(`A2A error: ${response.error.message} (code: ${response.error.code})`);
1187
+ }
1188
+ return response.result.task;
1189
+ }
1190
+ /**
1191
+ * Convert legacy TasksSendParams to spec SendMessageRequest format
1192
+ */
1193
+ toSendMessageRequest(params) {
1194
+ return {
1195
+ message: {
1196
+ messageId: `msg_${Date.now()}`,
1197
+ role: "user",
1198
+ parts: params.message.parts,
1199
+ contextId: params.contextId,
1200
+ taskId: params.message.taskId
1201
+ },
1202
+ metadata: {
1203
+ ...params.metadata,
1204
+ ...params.skill ? { skill: params.skill } : {}
1205
+ }
1206
+ };
1207
+ }
1208
+ async sendJsonRpc(request) {
1209
+ const response = await fetch(this.rpcUrl, {
1210
+ method: "POST",
1211
+ headers: {
1212
+ "Content-Type": "application/json",
1213
+ ...this.getHeaders()
1214
+ },
1215
+ body: JSON.stringify(request)
1216
+ });
1217
+ if (!response.ok) {
1218
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1219
+ }
1220
+ return response.json();
1221
+ }
1222
+ getHeaders() {
1223
+ const headers = { ...this.headers };
1224
+ if (this.apiKey) {
1225
+ if (this.apiKey.startsWith("a2a_")) {
1226
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
1227
+ } else {
1228
+ headers["X-API-Key"] = this.apiKey;
1229
+ }
1230
+ }
1231
+ return headers;
1232
+ }
1233
+ };
1234
+ function createRuntypeA2AClient(options) {
1235
+ const { productId, surfaceId, apiKey, environment = "production" } = options;
1236
+ const baseUrls = {
1237
+ production: "https://api.runtype.com",
1238
+ staging: "https://api.runtype-staging.com",
1239
+ local: "http://localhost:8787"
1240
+ };
1241
+ const baseUrl = `${baseUrls[environment]}/v1/products/${productId}/surfaces/${surfaceId}/a2a`;
1242
+ return new A2AClient({
1243
+ baseUrl,
1244
+ apiKey
1245
+ });
1246
+ }
1247
+
1248
+ export {
1249
+ JSON_RPC_ERRORS,
1250
+ executeTimeSkill,
1251
+ executeTask,
1252
+ executeTaskStreaming,
1253
+ executeEcho,
1254
+ executeEchoStreaming,
1255
+ DEFAULT_SKILLS,
1256
+ DEFAULT_LLM_CONFIG,
1257
+ createA2AServer,
1258
+ A2AClient,
1259
+ createRuntypeA2AClient
1260
+ };
1261
+ //# sourceMappingURL=chunk-KIZQKOZR.js.map