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