@distri/core 0.2.4 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -3,6 +3,90 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
5
  // src/types.ts
6
+ function isArrayParts(result) {
7
+ return Array.isArray(result) && result[0].part_type;
8
+ }
9
+ function createSuccessfulToolResult(toolCallId, toolName, result) {
10
+ const parts = isArrayParts(result) ? result : [{
11
+ part_type: "data",
12
+ data: {
13
+ result,
14
+ success: true,
15
+ error: void 0
16
+ }
17
+ }];
18
+ return {
19
+ tool_call_id: toolCallId,
20
+ tool_name: toolName,
21
+ parts
22
+ };
23
+ }
24
+ function createFailedToolResult(toolCallId, toolName, error, result) {
25
+ return {
26
+ tool_call_id: toolCallId,
27
+ tool_name: toolName,
28
+ parts: [{
29
+ part_type: "data",
30
+ data: {
31
+ result: result ?? `Tool execution failed: ${error}`,
32
+ success: false,
33
+ error
34
+ }
35
+ }]
36
+ };
37
+ }
38
+ function isDataPart(part) {
39
+ return typeof part === "object" && part !== null && "part_type" in part && part.part_type === "data" && "data" in part;
40
+ }
41
+ function isToolResultData(data) {
42
+ return typeof data === "object" && data !== null && "success" in data && typeof data.success === "boolean";
43
+ }
44
+ function extractToolResultData(toolResult) {
45
+ if (!toolResult.parts || !Array.isArray(toolResult.parts) || toolResult.parts.length === 0) {
46
+ return null;
47
+ }
48
+ const firstPart = toolResult.parts[0];
49
+ if (isDataPart(firstPart)) {
50
+ const data = firstPart.data;
51
+ if (isToolResultData(data)) {
52
+ return {
53
+ result: data.result,
54
+ success: data.success,
55
+ error: data.error
56
+ };
57
+ }
58
+ if (typeof data === "string") {
59
+ try {
60
+ const parsed = JSON.parse(data);
61
+ if (isToolResultData(parsed)) {
62
+ return {
63
+ result: parsed.result,
64
+ success: parsed.success,
65
+ error: parsed.error
66
+ };
67
+ }
68
+ return {
69
+ result: parsed,
70
+ success: true,
71
+ error: void 0
72
+ };
73
+ } catch {
74
+ return {
75
+ result: data,
76
+ success: true,
77
+ error: void 0
78
+ };
79
+ }
80
+ }
81
+ return {
82
+ result: data,
83
+ success: true,
84
+ error: void 0
85
+ };
86
+ }
87
+ return null;
88
+ }
89
+ var DEFAULT_BASE_URL = "https://api.distri.dev";
6
90
  var DistriError = class extends Error {
7
91
  constructor(message, code, details) {
8
92
  super(message);
@@ -36,14 +120,8 @@ function isDistriMessage(event) {
36
120
  function isDistriEvent(event) {
37
121
  return "type" in event && "data" in event;
38
122
  }
39
- function isDistriPlan(event) {
40
- return "steps" in event && Array.isArray(event.steps) && "reasoning" in event;
41
- }
42
- function isDistriArtifact(event) {
43
- return "type" in event && "timestamp" in event && "id" in event;
44
- }
45
123
 
46
- // ../../node_modules/.pnpm/@a2a-js+sdk@https+++codeload.github.com+v3g42+a2a-js+tar.gz+51444c9/node_modules/@a2a-js/sdk/dist/chunk-CUGIRVQB.js
124
+ // ../../../node_modules/.pnpm/@a2a-js+sdk@https+++codeload.github.com+v3g42+a2a-js+tar.gz+51444c9/node_modules/@a2a-js/sdk/dist/chunk-CUGIRVQB.js
47
125
  var A2AClient = class {
48
126
  /**
49
127
  * Constructs an A2AClient instance.
@@ -429,32 +507,56 @@ function convertA2AStatusUpdateToDistri(statusUpdate) {
429
507
  }
430
508
  const metadata = statusUpdate.metadata;
431
509
  switch (metadata.type) {
432
- case "run_started":
433
- return {
510
+ case "run_started": {
511
+ const runStartedResult = {
434
512
  type: "run_started",
435
- data: {}
513
+ data: {
514
+ runId: statusUpdate.runId,
515
+ taskId: statusUpdate.taskId
516
+ }
436
517
  };
437
- case "run_finished":
438
- return {
518
+ return runStartedResult;
519
+ }
520
+ case "run_error": {
521
+ const runErrorResult = {
522
+ type: "run_error",
523
+ data: {
524
+ message: statusUpdate.error,
525
+ code: statusUpdate.code
526
+ }
527
+ };
528
+ return runErrorResult;
529
+ }
530
+ case "run_finished": {
531
+ const runFinishedResult = {
439
532
  type: "run_finished",
440
- data: {}
533
+ data: {
534
+ runId: statusUpdate.runId,
535
+ taskId: statusUpdate.taskId
536
+ }
441
537
  };
442
- case "plan_started":
443
- return {
538
+ return runFinishedResult;
539
+ }
540
+ case "plan_started": {
541
+ const planStartedResult = {
444
542
  type: "plan_started",
445
543
  data: {
446
544
  initial_plan: metadata.initial_plan
447
545
  }
448
546
  };
449
- case "plan_finished":
450
- return {
547
+ return planStartedResult;
548
+ }
549
+ case "plan_finished": {
550
+ const planFinishedResult = {
451
551
  type: "plan_finished",
452
552
  data: {
453
553
  total_steps: metadata.total_steps
454
554
  }
455
555
  };
456
- case "step_started":
457
- return {
556
+ return planFinishedResult;
557
+ }
558
+ case "step_started": {
559
+ const stepStartedResult = {
458
560
  type: "step_started",
459
561
  data: {
460
562
  step_id: metadata.step_id,
@@ -462,8 +564,10 @@ function convertA2AStatusUpdateToDistri(statusUpdate) {
462
564
  step_index: metadata.step_index || 0
463
565
  }
464
566
  };
465
- case "step_completed":
466
- return {
567
+ return stepStartedResult;
568
+ }
569
+ case "step_completed": {
570
+ const stepCompletedResult = {
467
571
  type: "step_completed",
468
572
  data: {
469
573
  step_id: metadata.step_id,
@@ -471,142 +575,132 @@ function convertA2AStatusUpdateToDistri(statusUpdate) {
471
575
  step_index: metadata.step_index || 0
472
576
  }
473
577
  };
474
- case "tool_execution_start":
475
- return {
476
- type: "tool_call_start",
578
+ return stepCompletedResult;
579
+ }
580
+ case "tool_execution_start": {
581
+ const toolStartResult = {
582
+ type: "tool_execution_start",
477
583
  data: {
478
584
  tool_call_id: metadata.tool_call_id,
479
585
  tool_call_name: metadata.tool_call_name || "Tool",
480
- parent_message_id: statusUpdate.taskId,
481
- is_external: true
586
+ parent_message_id: statusUpdate.taskId
482
587
  }
483
588
  };
484
- case "tool_execution_end":
485
- return {
486
- type: "tool_call_end",
589
+ return toolStartResult;
590
+ }
591
+ case "tool_execution_end": {
592
+ const toolEndResult = {
593
+ type: "tool_execution_end",
487
594
  data: {
488
595
  tool_call_id: metadata.tool_call_id
489
596
  }
490
597
  };
491
- case "text_message_start":
492
- return {
598
+ return toolEndResult;
599
+ }
600
+ case "text_message_start": {
601
+ const textStartResult = {
493
602
  type: "text_message_start",
494
603
  data: {
495
604
  message_id: metadata.message_id,
605
+ step_id: metadata.step_id || "",
496
606
  role: metadata.role === "assistant" ? "assistant" : "user"
497
607
  }
498
608
  };
499
- case "text_message_content":
500
- return {
609
+ return textStartResult;
610
+ }
611
+ case "text_message_content": {
612
+ const textContentResult = {
501
613
  type: "text_message_content",
502
614
  data: {
503
615
  message_id: metadata.message_id,
616
+ step_id: metadata.step_id || "",
504
617
  delta: metadata.delta || ""
505
618
  }
506
619
  };
507
- case "text_message_end":
508
- return {
620
+ return textContentResult;
621
+ }
622
+ case "text_message_end": {
623
+ const textEndResult = {
509
624
  type: "text_message_end",
510
625
  data: {
511
- message_id: metadata.message_id
626
+ message_id: metadata.message_id,
627
+ step_id: metadata.step_id || ""
512
628
  }
513
629
  };
514
- default:
630
+ return textEndResult;
631
+ }
632
+ case "tool_calls": {
633
+ const toolCallsResult = {
634
+ type: "tool_calls",
635
+ data: {
636
+ tool_calls: metadata.tool_calls || []
637
+ }
638
+ };
639
+ return toolCallsResult;
640
+ }
641
+ case "tool_results": {
642
+ const toolResultsResult = {
643
+ type: "tool_results",
644
+ data: {
645
+ results: metadata.results || []
646
+ }
647
+ };
648
+ return toolResultsResult;
649
+ }
650
+ case "browser_screenshot": {
651
+ const browserScreenshotResult = {
652
+ type: "browser_screenshot",
653
+ data: {
654
+ image: metadata.image || "",
655
+ format: metadata.format,
656
+ filename: metadata.filename,
657
+ size: metadata.size,
658
+ timestamp_ms: metadata.timestamp_ms
659
+ }
660
+ };
661
+ return browserScreenshotResult;
662
+ }
663
+ case "inline_hook_requested": {
664
+ const hookRequested = {
665
+ type: "inline_hook_requested",
666
+ data: {
667
+ hook_id: metadata.request?.hook_id || metadata.hook_id || "",
668
+ hook: metadata.request?.hook || metadata.hook || "",
669
+ context: metadata.request?.context || metadata.context || {
670
+ agent_id: statusUpdate.agentId,
671
+ thread_id: statusUpdate.contextId,
672
+ task_id: statusUpdate.taskId,
673
+ run_id: statusUpdate.agentId
674
+ },
675
+ timeout_ms: metadata.request?.timeout_ms || metadata.timeout_ms,
676
+ fire_and_forget: metadata.request?.fire_and_forget ?? metadata.fire_and_forget,
677
+ message: metadata.request?.message || metadata.message,
678
+ plan: metadata.request?.plan || metadata.plan,
679
+ result: metadata.request?.result || metadata.result
680
+ }
681
+ };
682
+ return hookRequested;
683
+ }
684
+ default: {
515
685
  console.warn(`Unhandled status update metadata type: ${metadata.type}`, metadata);
516
- return {
686
+ const defaultResult = {
517
687
  type: "run_started",
518
- data: { metadata }
688
+ data: {
689
+ runId: statusUpdate.runId,
690
+ taskId: statusUpdate.taskId
691
+ }
519
692
  };
693
+ return defaultResult;
694
+ }
520
695
  }
521
696
  }
522
- function convertA2AArtifactToDistri(artifact) {
523
- if (!artifact || !artifact.parts || !Array.isArray(artifact.parts)) {
524
- return null;
525
- }
526
- const part = artifact.parts[0];
527
- if (!part || part.kind !== "data" || !part.data) {
528
- return null;
529
- }
530
- const data = part.data;
531
- if (data.type === "llm_response") {
532
- const hasContent = data.content && data.content.trim() !== "";
533
- const hasToolCalls = data.tool_calls && Array.isArray(data.tool_calls) && data.tool_calls.length > 0;
534
- const isExternal = data.is_external;
535
- if (hasToolCalls) {
536
- const executionResult2 = {
537
- id: data.id || artifact.artifactId,
538
- type: "llm_response",
539
- timestamp: data.timestamp || data.created_at || Date.now(),
540
- content: data.content?.trim() || "",
541
- tool_calls: data.tool_calls,
542
- step_id: data.step_id,
543
- success: data.success,
544
- rejected: data.rejected,
545
- reason: data.reason,
546
- is_external: isExternal
547
- };
548
- return executionResult2;
549
- } else {
550
- const parts = [];
551
- if (hasContent) {
552
- parts.push({ type: "text", text: data.content });
553
- }
554
- const distriMessage = {
555
- id: artifact.artifactId,
556
- role: "assistant",
557
- parts,
558
- created_at: data.timestamp || data.created_at || (/* @__PURE__ */ new Date()).toISOString()
559
- };
560
- return distriMessage;
561
- }
562
- }
563
- if (data.type === "tool_results") {
564
- const executionResult2 = {
565
- id: data.id || artifact.artifactId,
566
- type: "tool_results",
567
- timestamp: data.timestamp || data.created_at || Date.now(),
568
- results: data.results || [],
569
- step_id: data.step_id,
570
- success: data.success,
571
- rejected: data.rejected,
572
- reason: data.reason
573
- };
574
- return executionResult2;
575
- }
576
- if (data.type === "plan") {
577
- const planResult = {
578
- id: data.id || artifact.artifactId,
579
- type: "plan",
580
- timestamp: data.timestamp || data.created_at || Date.now(),
581
- reasoning: data.reasoning || "",
582
- steps: data.steps || []
583
- };
584
- return planResult;
585
- }
586
- const executionResult = {
587
- id: artifact.artifactId,
588
- type: "artifact",
589
- timestamp: Date.now(),
590
- data,
591
- artifactId: artifact.artifactId,
592
- name: artifact.name || "",
593
- description: artifact.description || null
594
- };
595
- return executionResult;
596
- }
597
697
  function decodeA2AStreamEvent(event) {
598
- if (event.artifactId && event.parts) {
599
- return convertA2AArtifactToDistri(event);
600
- }
601
698
  if (event.kind === "message") {
602
699
  return convertA2AMessageToDistri(event);
603
700
  }
604
701
  if (event.kind === "status-update") {
605
702
  return convertA2AStatusUpdateToDistri(event);
606
703
  }
607
- if (event.kind === "artifact-update") {
608
- return convertA2AArtifactToDistri(event);
609
- }
610
704
  return null;
611
705
  }
612
706
  function processA2AStreamData(streamData) {
@@ -632,28 +726,26 @@ function processA2AMessagesData(data) {
632
726
  function convertA2APartToDistri(a2aPart) {
633
727
  switch (a2aPart.kind) {
634
728
  case "text":
635
- return { type: "text", text: a2aPart.text };
729
+ return { part_type: "text", data: a2aPart.text };
636
730
  case "file":
637
731
  if ("uri" in a2aPart.file) {
638
- return { type: "image_url", image: { mime_type: a2aPart.file.mimeType, url: a2aPart.file.uri } };
732
+ const fileUrl = { type: "url", mime_type: a2aPart.file.mimeType || "application/octet-stream", url: a2aPart.file.uri || "" };
733
+ return { part_type: "image", data: fileUrl };
639
734
  } else {
640
- return { type: "image_bytes", image: { mime_type: a2aPart.file.mimeType, data: a2aPart.file.bytes } };
735
+ const fileBytes = { type: "bytes", mime_type: a2aPart.file.mimeType || "application/octet-stream", data: a2aPart.file.bytes || "" };
736
+ return { part_type: "image", data: fileBytes };
641
737
  }
642
738
  case "data":
643
739
  switch (a2aPart.data.part_type) {
644
740
  case "tool_call":
645
- return { type: "tool_call", tool_call: a2aPart.data };
741
+ return { part_type: "tool_call", data: a2aPart.data };
646
742
  case "tool_result":
647
- return { type: "tool_result", tool_result: a2aPart.data };
648
- case "code_observation":
649
- return { type: "code_observation", thought: a2aPart.data.thought, code: a2aPart.data.code };
650
- case "plan":
651
- return { type: "plan", plan: a2aPart.data.plan };
743
+ return { part_type: "tool_result", data: a2aPart.data };
652
744
  default:
653
- return { type: "data", data: a2aPart.data };
745
+ return { part_type: "data", data: a2aPart.data };
654
746
  }
655
747
  default:
656
- return { type: "text", text: JSON.stringify(a2aPart) };
748
+ return { part_type: "text", data: JSON.stringify(a2aPart) };
657
749
  }
658
750
  }
659
751
  function convertDistriMessageToA2A(distriMessage, context) {
@@ -678,52 +770,95 @@ function convertDistriMessageToA2A(distriMessage, context) {
678
770
  parts: distriMessage.parts.map(convertDistriPartToA2A),
679
771
  kind: "message",
680
772
  contextId: context.thread_id,
681
- taskId: context.run_id
773
+ taskId: context.task_id || context.run_id || void 0
682
774
  };
683
775
  }
684
776
  function convertDistriPartToA2A(distriPart) {
685
- switch (distriPart.type) {
777
+ let result;
778
+ switch (distriPart.part_type) {
686
779
  case "text":
687
- return { kind: "text", text: distriPart.text };
688
- case "image_url":
689
- return { kind: "file", file: { mimeType: distriPart.image.mime_type, uri: distriPart.image.url } };
690
- case "image_bytes":
691
- return { kind: "file", file: { mimeType: distriPart.image.mime_type, bytes: distriPart.image.data } };
780
+ result = { kind: "text", text: distriPart.data };
781
+ break;
782
+ case "image":
783
+ if ("url" in distriPart.data) {
784
+ const fileUri = { mimeType: distriPart.data.mime_type, uri: distriPart.data.url };
785
+ result = { kind: "file", file: fileUri };
786
+ } else {
787
+ const fileBytes = { mimeType: distriPart.data.mime_type, bytes: distriPart.data.data };
788
+ result = { kind: "file", file: fileBytes };
789
+ }
790
+ break;
692
791
  case "tool_call":
693
- return { kind: "data", data: { part_type: "tool_call", tool_call: distriPart.tool_call } };
694
- case "tool_result":
695
- let val = {
792
+ result = {
696
793
  kind: "data",
697
794
  data: {
698
- tool_call_id: distriPart.tool_result.tool_call_id,
699
- tool_name: distriPart.tool_result.tool_name,
700
- result: distriPart.tool_result.result,
701
- part_type: "tool_result"
795
+ part_type: "tool_call",
796
+ data: distriPart.data
702
797
  }
703
798
  };
704
- return val;
705
- case "code_observation":
706
- return { kind: "data", data: { ...distriPart, part_type: "code_observation" } };
707
- case "plan":
708
- return { kind: "data", data: { ...distriPart, part_type: "plan" } };
709
- case "data":
710
- return { kind: "data", ...distriPart.data };
799
+ break;
800
+ case "tool_result": {
801
+ const toolResult = distriPart.data;
802
+ const parts = toolResult.parts.map((part) => {
803
+ if ("type" in part && part.type === "data") {
804
+ return {
805
+ part_type: "data",
806
+ data: part.data
807
+ };
808
+ } else if ("part_type" in part) {
809
+ return part;
810
+ } else {
811
+ return {
812
+ part_type: "data",
813
+ data: part
814
+ };
815
+ }
816
+ });
817
+ result = {
818
+ kind: "data",
819
+ data: {
820
+ part_type: "tool_result",
821
+ data: {
822
+ tool_call_id: toolResult.tool_call_id,
823
+ tool_name: toolResult.tool_name,
824
+ parts
825
+ }
826
+ }
827
+ };
828
+ break;
829
+ }
830
+ case "data": {
831
+ const dataValue = distriPart.data;
832
+ if (dataValue === null || typeof dataValue !== "object" || Array.isArray(dataValue)) {
833
+ result = { kind: "data", data: { value: dataValue } };
834
+ } else {
835
+ const dataObj = dataValue;
836
+ result = { kind: "data", data: dataObj };
837
+ }
838
+ break;
839
+ }
711
840
  }
841
+ return result;
712
842
  }
713
843
  function extractTextFromDistriMessage(message) {
714
- return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("\n");
844
+ return message.parts.filter((part) => part.part_type === "text").map((part) => part.data).join("\n");
715
845
  }
716
846
  function extractToolCallsFromDistriMessage(message) {
717
- return message.parts.filter((part) => part.type === "tool_call").map((part) => part.tool_call);
847
+ return message.parts.filter((part) => part.part_type === "tool_call").map((part) => part.data);
718
848
  }
719
849
  function extractToolResultsFromDistriMessage(message) {
720
- return message.parts.filter((part) => part.type === "tool_result").map((part) => part.tool_result);
850
+ return message.parts.filter((part) => part.part_type === "tool_result").map((part) => part.data);
721
851
  }
722
852
 
723
853
  // src/distri-client.ts
724
- var DistriClient = class {
854
+ var _DistriClient = class _DistriClient {
725
855
  constructor(config) {
726
856
  this.agentClients = /* @__PURE__ */ new Map();
857
+ const headers = { ...config.headers };
858
+ this.accessToken = config.accessToken;
859
+ this.refreshToken = config.refreshToken;
860
+ this.tokenRefreshSkewMs = config.tokenRefreshSkewMs ?? 6e4;
861
+ this.onTokenRefresh = config.onTokenRefresh;
727
862
  this.config = {
728
863
  baseUrl: config.baseUrl.replace(/\/$/, ""),
729
864
  apiVersion: config.apiVersion || "v1",
@@ -731,10 +866,362 @@ var DistriClient = class {
731
866
  retryAttempts: config.retryAttempts || 3,
732
867
  retryDelay: config.retryDelay || 1e3,
733
868
  debug: config.debug || false,
734
- headers: config.headers || {},
869
+ headers,
735
870
  interceptor: config.interceptor || ((init) => Promise.resolve(init))
736
871
  };
737
- this.debug("DistriClient initialized with config:", this.config);
872
+ this.debug("DistriClient initialized with config:", {
873
+ baseUrl: this.config.baseUrl,
874
+ hasAccessToken: !!this.accessToken,
875
+ hasRefreshToken: !!this.refreshToken,
876
+ timeout: this.config.timeout
877
+ });
878
+ }
879
+ /**
880
+ * Create a client with default cloud configuration.
881
+ *
882
+ * @param overrides - Optional overrides for the default config
883
+ */
884
+ static create(overrides = {}) {
885
+ return new _DistriClient({
886
+ baseUrl: DEFAULT_BASE_URL,
887
+ ...overrides
888
+ });
889
+ }
890
+ /**
891
+ * Check if this client has authentication configured.
892
+ */
893
+ hasAuth() {
894
+ return !!this.accessToken || !!this.refreshToken;
895
+ }
896
+ /**
897
+ * Check if this client is configured for local development.
898
+ */
899
+ isLocal() {
900
+ return this.config.baseUrl.includes("localhost") || this.config.baseUrl.includes("127.0.0.1");
901
+ }
902
+ /**
903
+ * Session store: set a value (optionally with expiry)
904
+ */
905
+ async setSessionValue(sessionId, key, value, expiry) {
906
+ const body = { key, value };
907
+ if (expiry) {
908
+ body.expiry = typeof expiry === "string" ? expiry : expiry.toISOString();
909
+ }
910
+ const resp = await this.fetch(`/session/${encodeURIComponent(sessionId)}/values`, {
911
+ method: "POST",
912
+ headers: {
913
+ "Content-Type": "application/json",
914
+ ...this.config.headers
915
+ },
916
+ body: JSON.stringify(body)
917
+ });
918
+ if (!resp.ok && resp.status !== 204) {
919
+ const errorData = await resp.json().catch(() => ({}));
920
+ throw new ApiError(errorData.error || "Failed to set session value", resp.status);
921
+ }
922
+ }
923
+ /**
924
+ * Session store: get a single value
925
+ */
926
+ async getSessionValue(sessionId, key) {
927
+ const resp = await this.fetch(`/session/${encodeURIComponent(sessionId)}/values/${encodeURIComponent(key)}`, {
928
+ method: "GET",
929
+ headers: {
930
+ ...this.config.headers
931
+ }
932
+ });
933
+ if (!resp.ok) {
934
+ const errorData = await resp.json().catch(() => ({}));
935
+ throw new ApiError(errorData.error || "Failed to get session value", resp.status);
936
+ }
937
+ const data = await resp.json().catch(() => ({ value: null }));
938
+ return data?.value ?? null;
939
+ }
940
+ /**
941
+ * Session store: get all values in a session
942
+ */
943
+ async getSessionValues(sessionId) {
944
+ const resp = await this.fetch(`/session/${encodeURIComponent(sessionId)}/values`, {
945
+ method: "GET",
946
+ headers: {
947
+ ...this.config.headers
948
+ }
949
+ });
950
+ if (!resp.ok) {
951
+ const errorData = await resp.json().catch(() => ({}));
952
+ throw new ApiError(errorData.error || "Failed to get session values", resp.status);
953
+ }
954
+ const data = await resp.json().catch(() => ({ values: {} }));
955
+ return data?.values ?? {};
956
+ }
957
+ /**
958
+ * Session store: delete a single key
959
+ */
960
+ async deleteSessionValue(sessionId, key) {
961
+ const resp = await this.fetch(`/session/${encodeURIComponent(sessionId)}/values/${encodeURIComponent(key)}`, {
962
+ method: "DELETE",
963
+ headers: {
964
+ ...this.config.headers
965
+ }
966
+ });
967
+ if (!resp.ok && resp.status !== 204) {
968
+ const errorData = await resp.json().catch(() => ({}));
969
+ throw new ApiError(errorData.error || "Failed to delete session value", resp.status);
970
+ }
971
+ }
972
+ /**
973
+ * Session store: clear all keys in a session
974
+ */
975
+ async clearSession(sessionId) {
976
+ const resp = await this.fetch(`/session/${encodeURIComponent(sessionId)}`, {
977
+ method: "DELETE",
978
+ headers: {
979
+ ...this.config.headers
980
+ }
981
+ });
982
+ if (!resp.ok && resp.status !== 204) {
983
+ const errorData = await resp.json().catch(() => ({}));
984
+ throw new ApiError(errorData.error || "Failed to clear session", resp.status);
985
+ }
986
+ }
987
+ /**
988
+ * Set additional user message parts for the next agent iteration.
989
+ * These parts will be appended to the user message in the prompt.
990
+ * @param sessionId - The thread/session ID
991
+ * @param parts - Array of DistriPart objects to append to user message
992
+ */
993
+ async setAdditionalUserParts(sessionId, parts) {
994
+ await this.setSessionValue(sessionId, _DistriClient.ADDITIONAL_PARTS_KEY, parts);
995
+ }
996
+ /**
997
+ * Get the current additional user message parts.
998
+ * @param sessionId - The thread/session ID
999
+ * @returns Array of DistriPart objects or null if not set
1000
+ */
1001
+ async getAdditionalUserParts(sessionId) {
1002
+ return this.getSessionValue(sessionId, _DistriClient.ADDITIONAL_PARTS_KEY);
1003
+ }
1004
+ /**
1005
+ * Clear/delete the additional user message parts.
1006
+ * @param sessionId - The thread/session ID
1007
+ */
1008
+ async clearAdditionalUserParts(sessionId) {
1009
+ await this.deleteSessionValue(sessionId, _DistriClient.ADDITIONAL_PARTS_KEY);
1010
+ }
1011
+ /**
1012
+ * Issue an access token + refresh token for temporary authentication.
1013
+ * Requires an existing authenticated session (bearer token).
1014
+ *
1015
+ * @returns Token response with access/refresh token strings
1016
+ * @throws ApiError if not authenticated or token issuance fails
1017
+ *
1018
+ * @example
1019
+ * ```typescript
1020
+ * const { access_token, refresh_token } = await client.issueToken();
1021
+ * // Persist the refresh token and use access_token for requests
1022
+ * ```
1023
+ */
1024
+ async issueToken() {
1025
+ const response = await this.fetch("/token", {
1026
+ method: "POST",
1027
+ headers: {
1028
+ "Content-Type": "application/json",
1029
+ ...this.config.headers
1030
+ }
1031
+ });
1032
+ if (!response.ok) {
1033
+ const errorData = await response.json().catch(() => ({}));
1034
+ throw new ApiError(errorData.error || "Failed to issue token", response.status);
1035
+ }
1036
+ const tokens = await response.json();
1037
+ if (!tokens?.access_token || !tokens?.refresh_token || typeof tokens?.expires_at !== "number") {
1038
+ throw new ApiError("Invalid token response", response.status);
1039
+ }
1040
+ this.applyTokens(tokens.access_token, tokens.refresh_token, false);
1041
+ return tokens;
1042
+ }
1043
+ /**
1044
+ * Get the current access/refresh tokens.
1045
+ */
1046
+ getTokens() {
1047
+ return { accessToken: this.accessToken, refreshToken: this.refreshToken };
1048
+ }
1049
+ /**
1050
+ * Update the access/refresh tokens in memory.
1051
+ */
1052
+ setTokens(tokens) {
1053
+ if (tokens.accessToken !== void 0) {
1054
+ this.accessToken = tokens.accessToken;
1055
+ }
1056
+ if (tokens.refreshToken !== void 0) {
1057
+ this.refreshToken = tokens.refreshToken;
1058
+ }
1059
+ }
1060
+ /**
1061
+ * Start streaming speech-to-text transcription via WebSocket
1062
+ */
1063
+ async streamingTranscription(options = {}) {
1064
+ const baseUrl = this.config.baseUrl;
1065
+ const wsUrl = baseUrl.replace("http://", "ws://").replace("https://", "wss://") + "/voice/stream";
1066
+ return new Promise((resolve, reject) => {
1067
+ const ws = new WebSocket(wsUrl);
1068
+ let isResolved = false;
1069
+ ws.onopen = () => {
1070
+ ws.send(JSON.stringify({ type: "start_session" }));
1071
+ options.onStart?.();
1072
+ if (!isResolved) {
1073
+ isResolved = true;
1074
+ resolve({
1075
+ sendAudio: (audioData) => {
1076
+ if (ws.readyState === WebSocket.OPEN) {
1077
+ ws.send(audioData);
1078
+ }
1079
+ },
1080
+ sendText: (text) => {
1081
+ if (ws.readyState === WebSocket.OPEN) {
1082
+ ws.send(JSON.stringify({ type: "text_chunk", text }));
1083
+ }
1084
+ },
1085
+ stop: () => {
1086
+ if (ws.readyState === WebSocket.OPEN) {
1087
+ ws.send(JSON.stringify({ type: "end_session" }));
1088
+ }
1089
+ },
1090
+ close: () => {
1091
+ ws.close();
1092
+ }
1093
+ });
1094
+ }
1095
+ };
1096
+ ws.onmessage = (event) => {
1097
+ try {
1098
+ const data = JSON.parse(event.data);
1099
+ switch (data.type) {
1100
+ case "text_chunk":
1101
+ options.onTranscript?.(data.text || "", data.is_final || false);
1102
+ break;
1103
+ case "session_started":
1104
+ this.debug("Speech-to-text session started");
1105
+ break;
1106
+ case "session_ended":
1107
+ this.debug("Speech-to-text session ended");
1108
+ options.onEnd?.();
1109
+ break;
1110
+ case "error": {
1111
+ const error = new Error(data.message || "WebSocket error");
1112
+ this.debug("Speech-to-text error:", error);
1113
+ options.onError?.(error);
1114
+ break;
1115
+ }
1116
+ default:
1117
+ this.debug("Unknown message type:", data.type);
1118
+ }
1119
+ } catch (error) {
1120
+ const parseError = new Error("Failed to parse WebSocket message");
1121
+ this.debug("Parse error:", parseError);
1122
+ options.onError?.(parseError);
1123
+ }
1124
+ };
1125
+ ws.onerror = (event) => {
1126
+ const error = new Error("WebSocket connection error");
1127
+ this.debug("WebSocket error:", event);
1128
+ options.onError?.(error);
1129
+ if (!isResolved) {
1130
+ isResolved = true;
1131
+ reject(error);
1132
+ }
1133
+ };
1134
+ ws.onclose = (event) => {
1135
+ this.debug("WebSocket closed:", event.code, event.reason);
1136
+ options.onEnd?.();
1137
+ };
1138
+ });
1139
+ }
1140
+ /**
1141
+ * Transcribe audio blob to text using speech-to-text API
1142
+ */
1143
+ async transcribe(audioBlob, config = {}) {
1144
+ try {
1145
+ const arrayBuffer = await audioBlob.arrayBuffer();
1146
+ const uint8Array = new Uint8Array(arrayBuffer);
1147
+ const base64String = btoa(String.fromCharCode(...uint8Array));
1148
+ const requestBody = {
1149
+ audio: base64String,
1150
+ model: config.model || "whisper-1",
1151
+ ...config.language && { language: config.language },
1152
+ ...config.temperature !== void 0 && { temperature: config.temperature }
1153
+ };
1154
+ this.debug("Transcribing audio:", {
1155
+ model: requestBody.model,
1156
+ language: config.language,
1157
+ audioSize: audioBlob.size
1158
+ });
1159
+ const response = await this.fetch(`/tts/transcribe`, {
1160
+ method: "POST",
1161
+ headers: {
1162
+ "Content-Type": "application/json",
1163
+ ...this.config.headers
1164
+ },
1165
+ body: JSON.stringify(requestBody)
1166
+ });
1167
+ if (!response.ok) {
1168
+ const errorData = await response.json().catch(() => ({}));
1169
+ const errorMessage = errorData.error || `Transcription failed: ${response.status}`;
1170
+ throw new ApiError(errorMessage, response.status);
1171
+ }
1172
+ const result = await response.json();
1173
+ const transcription = result.text || "";
1174
+ this.debug("Transcription result:", { text: transcription });
1175
+ return transcription;
1176
+ } catch (error) {
1177
+ if (error instanceof ApiError) throw error;
1178
+ throw new DistriError("Failed to transcribe audio", "TRANSCRIPTION_ERROR", error);
1179
+ }
1180
+ }
1181
+ async getConfiguration() {
1182
+ const response = await this.fetch(`/configuration`, {
1183
+ method: "GET",
1184
+ headers: {
1185
+ ...this.config.headers
1186
+ }
1187
+ });
1188
+ if (!response.ok) {
1189
+ const errorData = await response.json().catch(() => ({}));
1190
+ throw new ApiError(errorData.error || "Failed to load configuration", response.status);
1191
+ }
1192
+ return response.json();
1193
+ }
1194
+ async updateConfiguration(configuration) {
1195
+ const response = await this.fetch(`/configuration`, {
1196
+ method: "PUT",
1197
+ headers: {
1198
+ "Content-Type": "application/json",
1199
+ ...this.config.headers
1200
+ },
1201
+ body: JSON.stringify(configuration)
1202
+ });
1203
+ if (!response.ok) {
1204
+ const errorData = await response.json().catch(() => ({}));
1205
+ throw new ApiError(errorData.error || "Failed to update configuration", response.status);
1206
+ }
1207
+ return response.json();
1208
+ }
1209
+ /**
1210
+ * Minimal LLM helper that proxies to the Distri server using Distri messages.
1211
+ */
1212
+ async llm(messages, tools = [], options) {
1213
+ const headers = { "Content-Type": "application/json" };
1214
+ const response = await this.fetch(`/llm/execute`, {
1215
+ method: "POST",
1216
+ headers,
1217
+ body: JSON.stringify({ messages, tools, ...options })
1218
+ });
1219
+ if (!response.ok) {
1220
+ const errorData = await response.json().catch(() => ({}));
1221
+ const message = errorData?.error || response.statusText || "LLM request failed";
1222
+ throw new ApiError(`LLM request failed: ${message}`, response.status);
1223
+ }
1224
+ return response.json();
738
1225
  }
739
1226
  /**
740
1227
  * Get all available agents from the Distri server
@@ -791,14 +1278,18 @@ var DistriClient = class {
791
1278
  * Get or create A2AClient for an agent
792
1279
  */
793
1280
  getA2AClient(agentId) {
794
- if (!this.agentClients.has(agentId)) {
1281
+ const agentUrl = `${this.config.baseUrl}/agents/${agentId}`;
1282
+ const existing = this.agentClients.get(agentId);
1283
+ if (!existing || existing.url !== agentUrl) {
795
1284
  const fetchFn = this.fetchAbsolute.bind(this);
796
- const agentUrl = `${this.config.baseUrl}/agents/${agentId}`;
797
1285
  const client = new A2AClient(agentUrl, fetchFn);
798
- this.agentClients.set(agentId, client);
799
- this.debug(`Created A2AClient for agent ${agentId} at ${agentUrl}`);
1286
+ this.agentClients.set(agentId, { url: agentUrl, client });
1287
+ this.debug(
1288
+ existing ? `Recreated A2AClient for agent ${agentId} with new URL ${agentUrl}` : `Created A2AClient for agent ${agentId} at ${agentUrl}`
1289
+ );
1290
+ return client;
800
1291
  }
801
- return this.agentClients.get(agentId);
1292
+ return existing.client;
802
1293
  }
803
1294
  /**
804
1295
  * Send a message to an agent
@@ -825,6 +1316,7 @@ var DistriClient = class {
825
1316
  * Send a streaming message to an agent
826
1317
  */
827
1318
  async *sendMessageStream(agentId, params) {
1319
+ console.log("sendMessageStream", agentId, params);
828
1320
  try {
829
1321
  const client = this.getA2AClient(agentId);
830
1322
  yield* await client.sendMessageStream(params);
@@ -930,18 +1422,195 @@ var DistriClient = class {
930
1422
  };
931
1423
  await this.sendMessage(threadId, params);
932
1424
  }
1425
+ /**
1426
+ * Complete an external tool call
1427
+ */
1428
+ async completeTool(agentId, result) {
1429
+ try {
1430
+ const response = await this.fetch(`/agents/${agentId}/complete-tool`, {
1431
+ method: "POST",
1432
+ headers: {
1433
+ "Content-Type": "application/json",
1434
+ ...this.config.headers
1435
+ },
1436
+ body: JSON.stringify({
1437
+ tool_call_id: result.tool_call_id,
1438
+ tool_response: result
1439
+ })
1440
+ });
1441
+ if (!response.ok) {
1442
+ throw new ApiError(`Failed to complete tool: ${response.statusText}`, response.status);
1443
+ }
1444
+ this.debug(`Tool completed: ${result.tool_name} (${result.tool_call_id}) for agent ${agentId}`);
1445
+ } catch (error) {
1446
+ if (error instanceof ApiError) throw error;
1447
+ throw new DistriError(`Failed to complete tool ${result.tool_name} (${result.tool_call_id}) for agent ${agentId}`, "COMPLETE_TOOL_ERROR", error);
1448
+ }
1449
+ }
1450
+ /**
1451
+ * Complete an inline hook with a mutation payload.
1452
+ */
1453
+ async completeInlineHook(hookId, mutation) {
1454
+ const response = await this.fetch(`/event/hooks`, {
1455
+ method: "POST",
1456
+ headers: {
1457
+ "Content-Type": "application/json",
1458
+ ...this.config.headers
1459
+ },
1460
+ body: JSON.stringify({
1461
+ hook_id: hookId,
1462
+ mutation
1463
+ })
1464
+ });
1465
+ if (!response.ok) {
1466
+ throw new ApiError(`Failed to complete inline hook: ${response.statusText}`, response.status);
1467
+ }
1468
+ }
933
1469
  /**
934
1470
  * Get the base URL for making direct requests
935
1471
  */
936
1472
  get baseUrl() {
937
1473
  return this.config.baseUrl;
938
1474
  }
1475
+ applyTokens(accessToken, refreshToken, notify) {
1476
+ this.accessToken = accessToken;
1477
+ this.refreshToken = refreshToken;
1478
+ if (notify && this.onTokenRefresh) {
1479
+ this.onTokenRefresh({ accessToken, refreshToken });
1480
+ }
1481
+ }
1482
+ async ensureAccessToken() {
1483
+ if (!this.refreshToken) {
1484
+ return;
1485
+ }
1486
+ if (!this.accessToken || this.isTokenExpiring(this.accessToken)) {
1487
+ try {
1488
+ await this.refreshTokens();
1489
+ } catch (error) {
1490
+ this.debug("Token refresh failed:", error);
1491
+ }
1492
+ }
1493
+ }
1494
+ async refreshTokens() {
1495
+ if (!this.refreshToken) {
1496
+ return;
1497
+ }
1498
+ if (!this.refreshPromise) {
1499
+ this.refreshPromise = this.performTokenRefresh().finally(() => {
1500
+ this.refreshPromise = void 0;
1501
+ });
1502
+ }
1503
+ return this.refreshPromise;
1504
+ }
1505
+ async performTokenRefresh() {
1506
+ const response = await this.fetchAbsolute(
1507
+ `${this.config.baseUrl}/token`,
1508
+ {
1509
+ method: "POST",
1510
+ headers: {
1511
+ "Content-Type": "application/json",
1512
+ ...this.config.headers
1513
+ },
1514
+ body: JSON.stringify({
1515
+ grant_type: "refresh_token",
1516
+ refresh_token: this.refreshToken
1517
+ })
1518
+ },
1519
+ { skipAuth: true, retryOnAuth: false }
1520
+ );
1521
+ if (!response.ok) {
1522
+ const errorData = await response.json().catch(() => ({}));
1523
+ throw new ApiError(errorData.error || "Failed to refresh token", response.status);
1524
+ }
1525
+ const tokens = await response.json();
1526
+ if (!tokens?.access_token || !tokens?.refresh_token) {
1527
+ throw new ApiError("Invalid token response", response.status);
1528
+ }
1529
+ this.applyTokens(tokens.access_token, tokens.refresh_token, true);
1530
+ }
1531
+ isTokenExpiring(token) {
1532
+ const expiresAt = this.getTokenExpiry(token);
1533
+ if (!expiresAt) {
1534
+ return false;
1535
+ }
1536
+ return expiresAt <= Date.now() + this.tokenRefreshSkewMs;
1537
+ }
1538
+ getTokenExpiry(token) {
1539
+ const payload = this.decodeJwtPayload(token);
1540
+ const exp = payload?.exp;
1541
+ if (typeof exp !== "number") {
1542
+ return null;
1543
+ }
1544
+ return exp * 1e3;
1545
+ }
1546
+ decodeJwtPayload(token) {
1547
+ const parts = token.split(".");
1548
+ if (parts.length < 2) {
1549
+ return null;
1550
+ }
1551
+ const decoded = this.decodeBase64Url(parts[1]);
1552
+ if (!decoded) {
1553
+ return null;
1554
+ }
1555
+ try {
1556
+ return JSON.parse(decoded);
1557
+ } catch {
1558
+ return null;
1559
+ }
1560
+ }
1561
+ decodeBase64Url(value) {
1562
+ const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
1563
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
1564
+ try {
1565
+ if (typeof atob === "function") {
1566
+ return atob(padded);
1567
+ }
1568
+ const buffer = globalThis.Buffer;
1569
+ if (typeof buffer !== "undefined") {
1570
+ return buffer.from(padded, "base64").toString("utf8");
1571
+ }
1572
+ } catch {
1573
+ return null;
1574
+ }
1575
+ return null;
1576
+ }
1577
+ applyAuthHeader(headers) {
1578
+ if (this.accessToken && !headers.has("authorization")) {
1579
+ headers.set("Authorization", `Bearer ${this.accessToken}`);
1580
+ }
1581
+ }
939
1582
  /**
940
1583
  * Enhanced fetch with retry logic
941
1584
  */
942
- async fetchAbsolute(url, initialInit) {
1585
+ async fetchAbsolute(url, initialInit, options) {
1586
+ const { skipAuth = false, retryOnAuth = true } = options ?? {};
943
1587
  const init = await this.config.interceptor(initialInit);
944
1588
  let lastError;
1589
+ const headers = new Headers();
1590
+ const applyHeaders = (src) => {
1591
+ if (!src) return;
1592
+ if (src instanceof Headers) {
1593
+ src.forEach((value, key) => headers.set(key, value));
1594
+ } else if (Array.isArray(src)) {
1595
+ src.forEach(([key, value]) => headers.set(key, value));
1596
+ } else if (typeof src === "object") {
1597
+ Object.entries(src).forEach(([key, value]) => {
1598
+ if (typeof value === "string") {
1599
+ headers.set(key, value);
1600
+ }
1601
+ });
1602
+ }
1603
+ };
1604
+ applyHeaders(this.config.headers);
1605
+ applyHeaders(init?.headers);
1606
+ const hasBody = init?.body !== void 0 && !(init.body instanceof FormData) && !(init.body instanceof Blob);
1607
+ if (!headers.has("content-type") && hasBody) {
1608
+ headers.set("Content-Type", "application/json");
1609
+ }
1610
+ if (!skipAuth) {
1611
+ await this.ensureAccessToken();
1612
+ this.applyAuthHeader(headers);
1613
+ }
945
1614
  for (let attempt = 0; attempt <= this.config.retryAttempts; attempt++) {
946
1615
  try {
947
1616
  const controller = new AbortController();
@@ -949,12 +1618,15 @@ var DistriClient = class {
949
1618
  const response = await fetch(url, {
950
1619
  ...init,
951
1620
  signal: controller.signal,
952
- headers: {
953
- ...this.config.headers,
954
- ...init?.headers
955
- }
1621
+ headers
956
1622
  });
957
1623
  clearTimeout(timeoutId);
1624
+ if (!skipAuth && retryOnAuth && response.status === 401 && this.refreshToken) {
1625
+ const refreshed = await this.refreshTokens().then(() => true).catch(() => false);
1626
+ if (refreshed) {
1627
+ return this.fetchAbsolute(url, initialInit, { skipAuth, retryOnAuth: false });
1628
+ }
1629
+ }
958
1630
  return response;
959
1631
  } catch (error) {
960
1632
  lastError = error instanceof Error ? error : new Error(String(error));
@@ -967,7 +1639,8 @@ var DistriClient = class {
967
1639
  throw lastError;
968
1640
  }
969
1641
  /**
970
- * Enhanced fetch with retry logic
1642
+ * Enhanced fetch with retry logic and auth headers.
1643
+ * Exposed publicly for extensions like DistriHomeClient.
971
1644
  */
972
1645
  async fetch(input, initialInit) {
973
1646
  const url = `${this.config.baseUrl}${input}`;
@@ -1009,7 +1682,7 @@ var DistriClient = class {
1009
1682
  id: id || uuidv4(),
1010
1683
  role,
1011
1684
  parts,
1012
- created_at
1685
+ created_at: created_at || (/* @__PURE__ */ new Date()).getTime()
1013
1686
  };
1014
1687
  }
1015
1688
  /**
@@ -1039,6 +1712,25 @@ var DistriClient = class {
1039
1712
  };
1040
1713
  }
1041
1714
  };
1715
+ // ============================================================
1716
+ // Additional User Message Parts API
1717
+ // ============================================================
1718
+ // These methods allow external tools to append parts (text, images)
1719
+ // to the user message in the next agent iteration.
1720
+ // The parts are stored under the key "__additional_user_parts".
1721
+ _DistriClient.ADDITIONAL_PARTS_KEY = "__additional_user_parts";
1722
+ // ============================================================
1723
+ // Token API
1724
+ // ============================================================
1725
+ // Issue access + refresh tokens for temporary authentication (e.g., frontend use)
1726
+ /**
1727
+ * Response from the token endpoint
1728
+ */
1729
+ _DistriClient.TokenType = {
1730
+ Main: "main",
1731
+ Short: "short"
1732
+ };
1733
+ var DistriClient = _DistriClient;
1042
1734
  function uuidv4() {
1043
1735
  if (typeof crypto?.randomUUID === "function") {
1044
1736
  return crypto.randomUUID();
@@ -1053,42 +1745,32 @@ function uuidv4() {
1053
1745
  }
1054
1746
 
1055
1747
  // src/agent.ts
1748
+ var ExternalToolValidationError = class extends DistriError {
1749
+ constructor(agentName, result) {
1750
+ super(
1751
+ result.message || "Missing required external tools for agent invocation.",
1752
+ "EXTERNAL_TOOL_VALIDATION_ERROR",
1753
+ {
1754
+ agentName,
1755
+ missingTools: result.missingTools,
1756
+ requiredTools: result.requiredTools,
1757
+ providedTools: result.providedTools
1758
+ }
1759
+ );
1760
+ this.name = "ExternalToolValidationError";
1761
+ this.agentName = agentName;
1762
+ this.missingTools = result.missingTools;
1763
+ this.requiredTools = result.requiredTools;
1764
+ this.providedTools = result.providedTools;
1765
+ }
1766
+ };
1056
1767
  var Agent = class _Agent {
1057
1768
  constructor(agentDefinition, client) {
1058
- this.tools = /* @__PURE__ */ new Map();
1769
+ this.hookHandlers = /* @__PURE__ */ new Map();
1770
+ this.defaultHookHandler = null;
1059
1771
  this.agentDefinition = agentDefinition;
1060
1772
  this.client = client;
1061
1773
  }
1062
- /**
1063
- * Add a tool to the agent (AG-UI style)
1064
- */
1065
- registerTool(tool) {
1066
- this.tools.set(tool.name, tool);
1067
- }
1068
- /**
1069
- * Add multiple tools at once
1070
- */
1071
- registerTools(tools) {
1072
- tools.forEach((tool) => this.registerTool(tool));
1073
- }
1074
- /**
1075
- * Remove a tool
1076
- */
1077
- unregisterTool(toolName) {
1078
- this.tools.delete(toolName);
1079
- }
1080
- /**
1081
- * Get all registered tools
1082
- */
1083
- getTools() {
1084
- return Array.from(this.tools.values());
1085
- }
1086
- /**
1087
- * Check if a tool is registered
1088
- */
1089
- hasTool(toolName) {
1090
- return this.tools.has(toolName);
1091
- }
1092
1774
  /**
1093
1775
  * Get agent information
1094
1776
  */
@@ -1101,9 +1783,18 @@ var Agent = class _Agent {
1101
1783
  get description() {
1102
1784
  return this.agentDefinition.description;
1103
1785
  }
1786
+ get agentType() {
1787
+ return this.agentDefinition.agent_type;
1788
+ }
1104
1789
  get iconUrl() {
1105
1790
  return this.agentDefinition.icon_url;
1106
1791
  }
1792
+ /**
1793
+ * Get the full agent definition (including backend tools)
1794
+ */
1795
+ getDefinition() {
1796
+ return this.agentDefinition;
1797
+ }
1107
1798
  /**
1108
1799
  * Fetch messages for a thread (public method for useChat)
1109
1800
  */
@@ -1113,51 +1804,154 @@ var Agent = class _Agent {
1113
1804
  /**
1114
1805
  * Direct (non-streaming) invoke
1115
1806
  */
1116
- async invoke(params) {
1117
- const enhancedParams = this.enhanceParamsWithTools(params);
1118
- console.log("enhancedParams", enhancedParams);
1807
+ async invoke(params, tools, hooks) {
1808
+ if (hooks) {
1809
+ this.registerHooks(hooks);
1810
+ }
1811
+ const enhancedParams = this.enhanceParamsWithTools(params, tools);
1119
1812
  return await this.client.sendMessage(this.agentDefinition.id, enhancedParams);
1120
1813
  }
1121
1814
  /**
1122
1815
  * Streaming invoke
1123
1816
  */
1124
- async invokeStream(params) {
1125
- const enhancedParams = this.enhanceParamsWithTools(params);
1126
- console.log("enhancedParams", enhancedParams);
1817
+ async invokeStream(params, tools, hooks) {
1818
+ if (hooks) {
1819
+ this.registerHooks(hooks);
1820
+ }
1821
+ const enhancedParams = this.enhanceParamsWithTools(params, tools);
1127
1822
  const a2aStream = this.client.sendMessageStream(this.agentDefinition.id, enhancedParams);
1128
- return async function* () {
1823
+ const self = this;
1824
+ return (async function* () {
1129
1825
  for await (const event of a2aStream) {
1130
1826
  const converted = decodeA2AStreamEvent(event);
1131
- if (converted) {
1827
+ if (converted && converted.type === "inline_hook_requested") {
1828
+ const hookReq = converted.data;
1829
+ const handler = self.hookHandlers.get(hookReq.hook) || self.defaultHookHandler;
1830
+ if (handler) {
1831
+ try {
1832
+ const mutation = await handler(hookReq);
1833
+ await self.client.completeInlineHook(hookReq.hook_id, mutation);
1834
+ } catch (err) {
1835
+ await self.client.completeInlineHook(hookReq.hook_id, { dynamic_values: {} });
1836
+ }
1837
+ } else {
1838
+ await self.client.completeInlineHook(hookReq.hook_id, { dynamic_values: {} });
1839
+ }
1840
+ yield converted;
1841
+ } else if (converted) {
1132
1842
  yield converted;
1133
1843
  }
1134
1844
  }
1135
- }();
1845
+ })();
1846
+ }
1847
+ /**
1848
+ * Validate that required external tools are registered before invoking.
1849
+ */
1850
+ validateExternalTools(tools = []) {
1851
+ const requiredTools = this.getRequiredExternalTools();
1852
+ const providedTools = tools.map((tool) => tool.name);
1853
+ if (requiredTools.length === 0) {
1854
+ return {
1855
+ isValid: true,
1856
+ requiredTools: [],
1857
+ providedTools,
1858
+ missingTools: []
1859
+ };
1860
+ }
1861
+ const providedSet = new Set(providedTools);
1862
+ const missingTools = requiredTools.filter((tool) => !providedSet.has(tool));
1863
+ const isValid = missingTools.length === 0;
1864
+ return {
1865
+ isValid,
1866
+ requiredTools,
1867
+ providedTools,
1868
+ missingTools,
1869
+ message: isValid ? void 0 : this.formatExternalToolValidationMessage(requiredTools, missingTools)
1870
+ };
1136
1871
  }
1137
1872
  /**
1138
1873
  * Enhance message params with tool definitions
1139
1874
  */
1140
- enhanceParamsWithTools(params) {
1141
- const tools = this.getTools();
1875
+ enhanceParamsWithTools(params, tools) {
1876
+ this.assertExternalTools(tools);
1877
+ const metadata = {
1878
+ ...params.metadata,
1879
+ external_tools: tools?.map((tool) => ({
1880
+ name: tool.name,
1881
+ description: tool.description,
1882
+ parameters: tool.parameters,
1883
+ is_final: tool.is_final
1884
+ })) || []
1885
+ };
1142
1886
  return {
1143
1887
  ...params,
1144
- metadata: {
1145
- ...params.metadata,
1146
- tools: tools.map((tool) => ({
1147
- name: tool.name,
1148
- description: tool.description,
1149
- input_schema: tool.input_schema
1150
- }))
1151
- }
1888
+ metadata
1152
1889
  };
1153
1890
  }
1891
+ assertExternalTools(tools) {
1892
+ const result = this.validateExternalTools(tools ?? []);
1893
+ if (!result.isValid) {
1894
+ throw new ExternalToolValidationError(this.agentDefinition.name || this.agentDefinition.id, result);
1895
+ }
1896
+ }
1897
+ getRequiredExternalTools() {
1898
+ const toolConfig = this.resolveToolConfig();
1899
+ if (!toolConfig?.external || !Array.isArray(toolConfig.external)) {
1900
+ return [];
1901
+ }
1902
+ return toolConfig.external.filter((tool) => typeof tool === "string" && tool.trim().length > 0);
1903
+ }
1904
+ resolveToolConfig() {
1905
+ const root = this.agentDefinition;
1906
+ return this.extractToolConfig(root) || this.extractToolConfig(root?.agent) || this.extractToolConfig(root?.definition);
1907
+ }
1908
+ extractToolConfig(candidate) {
1909
+ if (!candidate) return null;
1910
+ const tools = candidate.tools;
1911
+ if (!tools || Array.isArray(tools) || typeof tools !== "object") {
1912
+ return null;
1913
+ }
1914
+ return tools;
1915
+ }
1916
+ formatExternalToolValidationMessage(requiredTools, missingTools) {
1917
+ const requiredList = requiredTools.join(", ");
1918
+ const missingList = missingTools.join(", ");
1919
+ return `Agent has external tools that are not registered: ${missingList}. This is an embedded agent that can run within the parent application. Register DistriWidget for embedding the parent component. Required tools: ${requiredList}.`;
1920
+ }
1921
+ /**
1922
+ * Register multiple hooks at once.
1923
+ */
1924
+ registerHooks(hooks, defaultHandler) {
1925
+ Object.entries(hooks).forEach(([hook, handler]) => {
1926
+ this.hookHandlers.set(hook, handler);
1927
+ });
1928
+ if (defaultHandler) {
1929
+ this.defaultHookHandler = defaultHandler;
1930
+ }
1931
+ }
1154
1932
  /**
1155
1933
  * Create an agent instance from an agent ID
1156
1934
  */
1157
1935
  static async create(agentIdOrDef, client) {
1158
1936
  const agentDefinition = typeof agentIdOrDef === "string" ? await client.getAgent(agentIdOrDef) : agentIdOrDef;
1937
+ const tools = agentDefinition?.resolved_tools || [];
1938
+ console.log("\u{1F916} Agent definition loaded:", {
1939
+ id: agentDefinition.id,
1940
+ name: agentDefinition.name,
1941
+ tools: tools.map((t) => ({
1942
+ name: t.name,
1943
+ type: "function"
1944
+ })) || [],
1945
+ toolCount: agentDefinition.tools?.length || 0
1946
+ });
1159
1947
  return new _Agent(agentDefinition, client);
1160
1948
  }
1949
+ /**
1950
+ * Complete an external tool call by sending the result back to the server
1951
+ */
1952
+ async completeTool(result) {
1953
+ await this.client.completeTool(this.agentDefinition.id, result);
1954
+ }
1161
1955
  /**
1162
1956
  * List all available agents
1163
1957
  */
@@ -1171,22 +1965,25 @@ export {
1171
1965
  Agent,
1172
1966
  ApiError,
1173
1967
  ConnectionError,
1968
+ DEFAULT_BASE_URL,
1174
1969
  DistriClient,
1175
1970
  DistriError,
1176
- convertA2AArtifactToDistri,
1971
+ ExternalToolValidationError,
1177
1972
  convertA2AMessageToDistri,
1178
1973
  convertA2APartToDistri,
1179
1974
  convertA2AStatusUpdateToDistri,
1180
1975
  convertDistriMessageToA2A,
1181
1976
  convertDistriPartToA2A,
1977
+ createFailedToolResult,
1978
+ createSuccessfulToolResult,
1182
1979
  decodeA2AStreamEvent,
1183
1980
  extractTextFromDistriMessage,
1184
1981
  extractToolCallsFromDistriMessage,
1982
+ extractToolResultData,
1185
1983
  extractToolResultsFromDistriMessage,
1186
- isDistriArtifact,
1984
+ isArrayParts,
1187
1985
  isDistriEvent,
1188
1986
  isDistriMessage,
1189
- isDistriPlan,
1190
1987
  processA2AMessagesData,
1191
1988
  processA2AStreamData,
1192
1989
  uuidv4