@ryubyte/dsh-a2a 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +41 -19
- package/README.md +27 -13
- package/lib/client.js +601 -443
- package/lib/index.js +185 -14
- package/lib/types/a2a-config.d.ts.map +1 -1
- package/lib/types/client/dashboard.css.d.ts +20 -0
- package/lib/types/client/dashboard.css.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +4 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/dashboard.d.ts +20 -0
- package/lib/types/dashboard.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/server.d.ts +33 -1
- package/lib/types/server.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/a2a-config.ts +15 -2
- package/src/client/dashboard.css.ts +295 -0
- package/src/client/index.ts +669 -265
- package/src/dashboard.ts +39 -0
- package/src/index.ts +138 -17
- package/src/server.ts +67 -1
package/src/dashboard.ts
CHANGED
|
@@ -102,6 +102,20 @@ export interface DashboardControlHooks {
|
|
|
102
102
|
serverStatus?: () => ControlResult;
|
|
103
103
|
/** Runtime-remove an outbound agent by its connection id. */
|
|
104
104
|
removeAgent?: (connectionId: string) => Promise<ControlResult>;
|
|
105
|
+
/** Update the outbound-server identity (AgentCard) at runtime (persisted). */
|
|
106
|
+
setServerIdentity?: (patch: {
|
|
107
|
+
agentName?: string;
|
|
108
|
+
agentDescription?: string;
|
|
109
|
+
agentVersion?: string;
|
|
110
|
+
baseUrl?: string;
|
|
111
|
+
endpointPath?: string;
|
|
112
|
+
skills?: Array<{ id: string; name: string; description: string; tags?: string[] }>;
|
|
113
|
+
}) => Promise<ControlResult>;
|
|
114
|
+
/** Update a saved outbound agent's advanced fields, reconnecting to apply. */
|
|
115
|
+
updateAgent?: (
|
|
116
|
+
connectionId: string,
|
|
117
|
+
patch: { timeoutMs?: number; mapSkills?: boolean; bearerToken?: string },
|
|
118
|
+
) => Promise<ControlResult>;
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
/** Registry of live A2A connections, both directions. */
|
|
@@ -311,6 +325,31 @@ export class DashboardRegistry {
|
|
|
311
325
|
return this.hooks.serverStatus
|
|
312
326
|
? Promise.resolve(this.hooks.serverStatus())
|
|
313
327
|
: { ok: false, message: 'server-status is not wired (server mode not mounted)' };
|
|
328
|
+
case 'set-server-identity': {
|
|
329
|
+
if (!this.hooks.setServerIdentity) {
|
|
330
|
+
return { ok: false, message: 'set-server-identity is not wired (server not mounted)' };
|
|
331
|
+
}
|
|
332
|
+
return this.hooks.setServerIdentity({
|
|
333
|
+
agentName: payload?.agentName as string | undefined,
|
|
334
|
+
agentDescription: payload?.agentDescription as string | undefined,
|
|
335
|
+
agentVersion: payload?.agentVersion as string | undefined,
|
|
336
|
+
baseUrl: payload?.baseUrl as string | undefined,
|
|
337
|
+
endpointPath: payload?.endpointPath as string | undefined,
|
|
338
|
+
skills: payload?.skills as
|
|
339
|
+
| Array<{ id: string; name: string; description: string; tags?: string[] }>
|
|
340
|
+
| undefined,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
case 'update-agent': {
|
|
344
|
+
if (!this.hooks.updateAgent) {
|
|
345
|
+
return { ok: false, message: 'update-agent is not wired (client mode not mounted)' };
|
|
346
|
+
}
|
|
347
|
+
return this.hooks.updateAgent(target, {
|
|
348
|
+
timeoutMs: typeof payload?.timeoutMs === 'number' ? (payload.timeoutMs as number) : undefined,
|
|
349
|
+
mapSkills: typeof payload?.mapSkills === 'boolean' ? (payload.mapSkills as boolean) : undefined,
|
|
350
|
+
bearerToken: (payload?.bearerToken as string) || undefined,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
314
353
|
default:
|
|
315
354
|
return { ok: false, message: `unknown action ${action}` };
|
|
316
355
|
}
|
package/src/index.ts
CHANGED
|
@@ -309,8 +309,9 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
309
309
|
if (!res.ok) logger?.error?.(`[a2a] failed to save ${res.path}: ${res.message}`);
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
-
// Find an initial (persisted) entry by live connection.
|
|
313
|
-
|
|
312
|
+
// Find an initial (persisted) entry by live connection. Returns the full
|
|
313
|
+
// entry (incl. bearerToken/timeoutMs/mapSkills) so callers can edit them.
|
|
314
|
+
const findInitial = (entry: { name: string; agentCardUrl: string }): { idx: number; item: (typeof initialAgents)[number] } | undefined => {
|
|
314
315
|
const idx = initialAgents.findIndex((a) => a.configured && a.name === entry.name && a.agentCardUrl === entry.agentCardUrl);
|
|
315
316
|
if (idx < 0) return undefined;
|
|
316
317
|
return { idx, item: initialAgents[idx] };
|
|
@@ -469,6 +470,40 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
469
470
|
dashboard.removeAgent(connectionId);
|
|
470
471
|
return { ok: true, message: `agent ${connectionId} removed` };
|
|
471
472
|
},
|
|
473
|
+
updateAgent: async (connectionId, patch) => {
|
|
474
|
+
const entry = live.get(connectionId);
|
|
475
|
+
const snap = dashboard.snapshot().outbound.find((o) => o.connectionId === connectionId);
|
|
476
|
+
const name = entry?.name ?? snap?.name;
|
|
477
|
+
const url = entry?.agentCardUrl ?? snap?.agentCardUrl;
|
|
478
|
+
const configured = entry?.configured ?? snap?.configured ?? false;
|
|
479
|
+
if (!name || !url) return { ok: false, message: `connection ${connectionId} not found` };
|
|
480
|
+
const found = findInitial({ name, agentCardUrl: url });
|
|
481
|
+
if (!found) return { ok: false, message: 'only saved (configured) agents can be edited' };
|
|
482
|
+
// Update persisted advanced fields, then reconnect so they bind — these
|
|
483
|
+
// apply at connect()/registerAgentTools time (like enableAgent).
|
|
484
|
+
if (patch.timeoutMs !== undefined) found.item.timeoutMs = patch.timeoutMs;
|
|
485
|
+
if (patch.mapSkills !== undefined) found.item.mapSkills = patch.mapSkills;
|
|
486
|
+
if (patch.bearerToken !== undefined) found.item.bearerToken = patch.bearerToken || undefined;
|
|
487
|
+
persistAgents();
|
|
488
|
+
const item = found.item;
|
|
489
|
+
// A disabled agent: persist only, don't bring it online.
|
|
490
|
+
if (item.enabled === false) {
|
|
491
|
+
return { ok: true, message: `agent ${connectionId} updated (config kept, still disabled)` };
|
|
492
|
+
}
|
|
493
|
+
if (entry) {
|
|
494
|
+
entry.disposeAll();
|
|
495
|
+
live.delete(connectionId);
|
|
496
|
+
}
|
|
497
|
+
dashboard.setAgentState(connectionId, 'reconnecting');
|
|
498
|
+
const newId = await connect(item.name, item.agentCardUrl, item.configured, {
|
|
499
|
+
bearerToken: item.bearerToken,
|
|
500
|
+
timeoutMs: item.timeoutMs,
|
|
501
|
+
mapSkills: item.mapSkills,
|
|
502
|
+
});
|
|
503
|
+
if (!newId) return { ok: false, message: `update of ${connectionId} failed to reconnect` };
|
|
504
|
+
dashboard.removeAgent(connectionId);
|
|
505
|
+
return { ok: true, message: `agent ${connectionId} updated as ${newId} (saved to ${configPath})` };
|
|
506
|
+
},
|
|
472
507
|
});
|
|
473
508
|
|
|
474
509
|
ctx.effect(() => {
|
|
@@ -501,7 +536,15 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
501
536
|
}
|
|
502
537
|
|
|
503
538
|
// ── server mode: expose DSH as an A2A agent ───────────────────────────────
|
|
504
|
-
|
|
539
|
+
// The server effect is ALWAYS mounted (not gated on `wantsServer`), so its
|
|
540
|
+
// control hooks (serverStatus / setServerEnabled / setServerIdentity) are
|
|
541
|
+
// wired even in a client-only profile — letting the UI create and enable the
|
|
542
|
+
// A2A server at runtime with no restart and no hand-edit of a2a.json. Route
|
|
543
|
+
// registration itself is gated on `enabled` (initialized from `wantsServer`),
|
|
544
|
+
// so a profile that didn't ask to serve starts with the routes off. Building
|
|
545
|
+
// the A2AServer + probing the executor is side-effect-free (no sessions are
|
|
546
|
+
// created until an inbound task actually arrives).
|
|
547
|
+
{
|
|
505
548
|
const store = new TaskStore();
|
|
506
549
|
const taskPeer = new Map<string, string>();
|
|
507
550
|
const pendingSettled = new Set<string>();
|
|
@@ -513,6 +556,24 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
513
556
|
// to 0.0.0.0, advertise a reachable LAN address, not loopback.
|
|
514
557
|
const baseUrl = mergedConfig.baseUrl ?? inferBaseUrl(webServer.host, webServer.port);
|
|
515
558
|
const endpointPath = mergedConfig.endpointPath ?? DEFAULT_ENDPOINT;
|
|
559
|
+
// Surface a stale pinned baseUrl: if the config pins a port that does
|
|
560
|
+
// not match the real listen port, the advertised AgentCard URL is
|
|
561
|
+
// unreachable. We don't silently rewrite an explicit pin (it may point
|
|
562
|
+
// at a reverse proxy), but we warn so the mismatch is diagnosable —
|
|
563
|
+
// clearing baseUrl in the UI returns it to auto-inference.
|
|
564
|
+
if (mergedConfig.baseUrl) {
|
|
565
|
+
try {
|
|
566
|
+
const pinnedPort = new URL(mergedConfig.baseUrl).port;
|
|
567
|
+
if (pinnedPort && Number(pinnedPort) !== webServer.port) {
|
|
568
|
+
logger?.warn?.(
|
|
569
|
+
`[a2a] pinned baseUrl ${mergedConfig.baseUrl} port ${pinnedPort} != listen port ${webServer.port}; ` +
|
|
570
|
+
`remote callers may be unreachable. Clear baseUrl in settings to auto-infer.`,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
} catch {
|
|
574
|
+
// malformed pinned URL — ignore (the card just advertises it as-is).
|
|
575
|
+
}
|
|
576
|
+
}
|
|
516
577
|
|
|
517
578
|
// ── executor precedence ───────────────────────────────────────────────
|
|
518
579
|
// 1. explicit `execute` (config injection) → use it
|
|
@@ -605,7 +666,10 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
605
666
|
);
|
|
606
667
|
|
|
607
668
|
// ── runtime-enabled route set ──────────────────────────────────────
|
|
608
|
-
|
|
669
|
+
// Start serving only if this profile asked to (mode server/both or an
|
|
670
|
+
// explicit server.enabled). A client-only profile mounts the effect but
|
|
671
|
+
// keeps routes off until the UI enables the server.
|
|
672
|
+
let enabled = wantsServer;
|
|
609
673
|
let disposers: Array<() => void> = [];
|
|
610
674
|
|
|
611
675
|
const registerRoutes = (): void => {
|
|
@@ -660,20 +724,28 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
660
724
|
disposers = [];
|
|
661
725
|
};
|
|
662
726
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
727
|
+
if (enabled) {
|
|
728
|
+
registerRoutes();
|
|
729
|
+
logger?.info?.(
|
|
730
|
+
`[a2a] A2A server mounted: AgentCard at ${baseUrl}/.well-known/agent-card.json, JSON-RPC at ${server.endpointPath}`,
|
|
731
|
+
);
|
|
732
|
+
} else {
|
|
733
|
+
logger?.info?.('[a2a] A2A server ready but not serving (enable via settings)');
|
|
734
|
+
}
|
|
667
735
|
|
|
668
736
|
// ── serve panel hooks ──────────────────────────────────────────────
|
|
737
|
+
// Read live values off the server: identity edits mutate the card in
|
|
738
|
+
// place, so a captured `const baseUrl` would go stale. `endpointPath`
|
|
739
|
+
// and each skill's `tags` are surfaced so the edit form can prefill.
|
|
669
740
|
const serverCard = (): Record<string, unknown> => ({
|
|
670
|
-
baseUrl,
|
|
741
|
+
baseUrl: server.getBaseUrl(),
|
|
671
742
|
agentName: server.card.name,
|
|
672
743
|
agentDescription: server.card.description,
|
|
673
744
|
agentVersion: server.card.version,
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
745
|
+
endpointPath: server.endpointPath,
|
|
746
|
+
endpoint: `${server.getBaseUrl()}${server.endpointPath}`,
|
|
747
|
+
agentCardUrl: `${server.getBaseUrl()}/.well-known/agent-card.json`,
|
|
748
|
+
skills: (server.card.skills ?? []).map((s) => ({ id: s.id, name: s.name, description: s.description, tags: s.tags })),
|
|
677
749
|
customExecutor: Boolean(mergedConfig.execute),
|
|
678
750
|
// How inbound tasks are handled: a config-injected executor, this
|
|
679
751
|
// harness's own agent session, or none (refuses until configured).
|
|
@@ -689,12 +761,61 @@ export function apply(ctx: Context, config: A2APluginConfig = {}) {
|
|
|
689
761
|
registerRoutes();
|
|
690
762
|
enabled = true;
|
|
691
763
|
logger?.info?.('[a2a] A2A server enabled');
|
|
692
|
-
|
|
764
|
+
} else {
|
|
765
|
+
unregisterRoutes();
|
|
766
|
+
enabled = false;
|
|
767
|
+
logger?.info?.('[a2a] A2A server disabled');
|
|
768
|
+
}
|
|
769
|
+
// Persist server.enabled so the choice survives restart (mergePersisted
|
|
770
|
+
// maps server.enabled → serverEnabled → wantsServer on next boot).
|
|
771
|
+
const next: PersistedA2AConfig = loadPersistedA2A();
|
|
772
|
+
next.server = { ...(next.server ?? {}), enabled };
|
|
773
|
+
const res = savePersistedA2A(next);
|
|
774
|
+
if (!res.ok) {
|
|
775
|
+
logger?.error?.(`[a2a] failed to persist server.enabled to ${res.path}: ${res.message}`);
|
|
776
|
+
return { ok: false, message: `applied in-memory but failed to save: ${res.message}` };
|
|
777
|
+
}
|
|
778
|
+
return { ok: true, message: enable ? 'A2A server enabled' : 'A2A server disabled' };
|
|
779
|
+
},
|
|
780
|
+
setServerIdentity: async (patch) => {
|
|
781
|
+
// baseUrl semantics: undefined = "not touched"; "" = "clear pin,
|
|
782
|
+
// return to auto-inference"; non-empty = explicit pin (reverse
|
|
783
|
+
// proxy / public domain). Empty must NOT reach updateCard (which
|
|
784
|
+
// would set the advertised URL to ""), so handle it here where the
|
|
785
|
+
// real listen address (webServer.host/port) is available.
|
|
786
|
+
const clearBaseUrl = patch.baseUrl === '';
|
|
787
|
+
const cardPatch = clearBaseUrl ? { ...patch, baseUrl: undefined } : patch;
|
|
788
|
+
const { endpointChanged } = server.updateCard(cardPatch);
|
|
789
|
+
if (clearBaseUrl) {
|
|
790
|
+
// Re-derive from the actual listen address and re-advertise.
|
|
791
|
+
server.setBaseUrl(inferBaseUrl(webServer.host, webServer.port));
|
|
792
|
+
}
|
|
793
|
+
// The JSON-RPC path moved: re-register on the new path, but only
|
|
794
|
+
// while serving (disabled → routes get the new path on next enable).
|
|
795
|
+
if (endpointChanged && enabled) {
|
|
796
|
+
unregisterRoutes();
|
|
797
|
+
registerRoutes();
|
|
798
|
+
}
|
|
799
|
+
// Persist the provided fields to a2a.json server.{...}.
|
|
800
|
+
const next: PersistedA2AConfig = loadPersistedA2A();
|
|
801
|
+
const s = { ...(next.server ?? {}) };
|
|
802
|
+
if (patch.agentName !== undefined) s.agentName = patch.agentName;
|
|
803
|
+
if (patch.agentDescription !== undefined) s.agentDescription = patch.agentDescription;
|
|
804
|
+
if (patch.agentVersion !== undefined) s.agentVersion = patch.agentVersion;
|
|
805
|
+
// Clear the persisted pin so it stays auto on restart; a non-empty
|
|
806
|
+
// value pins it; undefined leaves the existing setting untouched.
|
|
807
|
+
if (clearBaseUrl) delete s.baseUrl;
|
|
808
|
+
else if (patch.baseUrl !== undefined) s.baseUrl = patch.baseUrl;
|
|
809
|
+
if (patch.endpointPath !== undefined) s.endpointPath = patch.endpointPath;
|
|
810
|
+
if (patch.skills !== undefined) s.skills = patch.skills;
|
|
811
|
+
next.server = s;
|
|
812
|
+
const res = savePersistedA2A(next);
|
|
813
|
+
if (!res.ok) {
|
|
814
|
+
logger?.error?.(`[a2a] failed to persist server identity to ${res.path}: ${res.message}`);
|
|
815
|
+
return { ok: false, message: `applied in-memory but failed to save: ${res.message}` };
|
|
693
816
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
logger?.info?.('[a2a] A2A server disabled');
|
|
697
|
-
return { ok: true, message: 'A2A server disabled' };
|
|
817
|
+
logger?.info?.(`[a2a] server identity updated (saved to ${res.path})`);
|
|
818
|
+
return { ok: true, message: `已更新对外服务 (saved to ${res.path})` };
|
|
698
819
|
},
|
|
699
820
|
setServerAuthToken: async (token) => {
|
|
700
821
|
server.setAuthToken(token);
|
package/src/server.ts
CHANGED
|
@@ -32,6 +32,20 @@ import {
|
|
|
32
32
|
|
|
33
33
|
export interface ServerSkill extends AgentSkill {}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Runtime-editable slice of the advertised AgentCard identity. Every field is
|
|
37
|
+
* optional; only the provided ones change. Applied in place by
|
|
38
|
+
* {@link A2AServer.updateCard} (mirrors setBaseUrl/setAuthToken).
|
|
39
|
+
*/
|
|
40
|
+
export interface CardIdentityPatch {
|
|
41
|
+
agentName?: string;
|
|
42
|
+
agentDescription?: string;
|
|
43
|
+
agentVersion?: string;
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
endpointPath?: string;
|
|
46
|
+
skills?: ServerSkill[];
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
export interface ServerOptions {
|
|
36
50
|
/** Public base URL (scheme + host + optional prefix) where this DSH is exposed. */
|
|
37
51
|
baseUrl: string;
|
|
@@ -155,7 +169,8 @@ export class A2AServer {
|
|
|
155
169
|
readonly options: ServerOptions;
|
|
156
170
|
readonly store: TaskStore;
|
|
157
171
|
readonly card: AgentCard;
|
|
158
|
-
|
|
172
|
+
/** JSON-RPC route path. Mutable: `updateCard` can move it at runtime. */
|
|
173
|
+
endpointPath: string;
|
|
159
174
|
private execute: TaskExecutor;
|
|
160
175
|
private listeners = new Set<(ev: StreamResponse) => void>();
|
|
161
176
|
private baseUrl: string;
|
|
@@ -181,6 +196,16 @@ export class A2AServer {
|
|
|
181
196
|
*/
|
|
182
197
|
setBaseUrl(baseUrl: string): void {
|
|
183
198
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
199
|
+
this.rebuildInterfaceUrls();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The base URL currently advertised in the AgentCard. */
|
|
203
|
+
getBaseUrl(): string {
|
|
204
|
+
return this.baseUrl;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Rebuild the advertised interface URL(s) from the current baseUrl + path. */
|
|
208
|
+
private rebuildInterfaceUrls(): void {
|
|
184
209
|
this.card.supportedInterfaces = this.card.supportedInterfaces.map((iface) => ({
|
|
185
210
|
...iface,
|
|
186
211
|
url: `${this.baseUrl}${this.endpointPath}`,
|
|
@@ -211,6 +236,47 @@ export class A2AServer {
|
|
|
211
236
|
return Boolean(this.options.authToken);
|
|
212
237
|
}
|
|
213
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Update the advertised identity (name/description/version/baseUrl/
|
|
241
|
+
* endpointPath/skills) at runtime, in place. Mirrors setBaseUrl/setAuthToken:
|
|
242
|
+
* mutates `this.options` and the existing `this.card` object's properties —
|
|
243
|
+
* never reassigns `this.card`, so the route handler closure that reads
|
|
244
|
+
* `server.card` per request keeps seeing the live card.
|
|
245
|
+
*
|
|
246
|
+
* Returns `{ endpointChanged }`: when the JSON-RPC path moved, the caller
|
|
247
|
+
* (which owns route registration) must re-register routes on the new path —
|
|
248
|
+
* `updateCard` only mutates the advertised URL, not the live HTTP routes.
|
|
249
|
+
*/
|
|
250
|
+
updateCard(patch: CardIdentityPatch): { endpointChanged: boolean } {
|
|
251
|
+
let endpointChanged = false;
|
|
252
|
+
if (patch.agentName !== undefined) {
|
|
253
|
+
this.options.agentName = patch.agentName;
|
|
254
|
+
this.card.name = patch.agentName;
|
|
255
|
+
}
|
|
256
|
+
if (patch.agentDescription !== undefined) {
|
|
257
|
+
this.options.agentDescription = patch.agentDescription;
|
|
258
|
+
this.card.description = patch.agentDescription;
|
|
259
|
+
}
|
|
260
|
+
if (patch.agentVersion !== undefined) {
|
|
261
|
+
this.options.agentVersion = patch.agentVersion;
|
|
262
|
+
this.card.version = patch.agentVersion;
|
|
263
|
+
}
|
|
264
|
+
if (patch.skills !== undefined) {
|
|
265
|
+
this.options.skills = patch.skills;
|
|
266
|
+
this.card.skills = patch.skills;
|
|
267
|
+
}
|
|
268
|
+
if (patch.endpointPath !== undefined && patch.endpointPath !== this.endpointPath) {
|
|
269
|
+
this.endpointPath = patch.endpointPath || '/a2a';
|
|
270
|
+
endpointChanged = true;
|
|
271
|
+
}
|
|
272
|
+
if (patch.baseUrl !== undefined) {
|
|
273
|
+
this.baseUrl = patch.baseUrl.replace(/\/$/, '');
|
|
274
|
+
}
|
|
275
|
+
// Any baseUrl/endpointPath change invalidates the advertised interface URL.
|
|
276
|
+
if (patch.baseUrl !== undefined || endpointChanged) this.rebuildInterfaceUrls();
|
|
277
|
+
return { endpointChanged };
|
|
278
|
+
}
|
|
279
|
+
|
|
214
280
|
private buildCard(): AgentCard {
|
|
215
281
|
const iface: AgentInterface = {
|
|
216
282
|
url: `${this.baseUrl.replace(/\/$/, '')}${this.endpointPath}`,
|