@truefoundry/assistant-ui-runtime 0.1.13 → 0.1.14

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.
@@ -47,6 +47,8 @@ vi.mock("./convertTurnMessages.js", async (importOriginal) => {
47
47
  const mockServer = {
48
48
  cancelSession: vi.fn().mockResolvedValue(undefined),
49
49
  listTurns: vi.fn(),
50
+ // Present so resume-capable paths are exercised; resumeTurnStream is mocked.
51
+ subscribeToTurn: vi.fn(),
50
52
  } as unknown as AgentChatServer;
51
53
 
52
54
  function snapshotWithAssistantMessage(
@@ -503,6 +505,119 @@ describe("useTrueFoundryAgentMessages", () => {
503
505
  );
504
506
  });
505
507
 
508
+ it("shows loaded history as running when the server cannot resume the turn", async () => {
509
+ const onError = vi.fn();
510
+ const runningTurn = {
511
+ id: "turn-running",
512
+ input: [{ type: "user.message", content: "keep going" }],
513
+ createdAt: new Date().toISOString(),
514
+ } as Turn;
515
+ vi.mocked(loadSessionSnapshot).mockResolvedValue(
516
+ replaceSessionSnapshot(createEmptySessionSnapshot(), {
517
+ runningTurn,
518
+ unstable_resume: true,
519
+ pendingUser: {
520
+ turnId: runningTurn.id,
521
+ content: "keep going",
522
+ createdAt: new Date(runningTurn.createdAt),
523
+ },
524
+ }),
525
+ );
526
+ const serverWithoutSubscribe = {
527
+ cancelSession: vi.fn().mockResolvedValue(undefined),
528
+ listTurns: vi.fn().mockResolvedValue({ data: [] }),
529
+ } as unknown as AgentChatServer;
530
+
531
+ const { result } = renderHook(() =>
532
+ useTrueFoundryAgentMessages({
533
+ server: serverWithoutSubscribe,
534
+ sessionId: "session-1",
535
+ onError,
536
+ }),
537
+ );
538
+
539
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
540
+ expect(resumeTurnStream).not.toHaveBeenCalled();
541
+ // The turn keeps running server-side, so history renders with a
542
+ // pending indicator rather than an endless skeleton.
543
+ await waitFor(() => expect(result.current.isRunning).toBe(true));
544
+ expect(result.current.messages[0]).toMatchObject({
545
+ role: "user",
546
+ content: [{ type: "text", text: "keep going" }],
547
+ });
548
+ // Waiting, not failing: hosts render this as state, not an error.
549
+ expect(result.current.resumeUnavailable).toBe(true);
550
+ expect(onError).not.toHaveBeenCalled();
551
+ });
552
+
553
+ it("clears the running state when cancelling a turn it could not resume", async () => {
554
+ const runningTurn = { id: "turn-running" } as Turn;
555
+ vi.mocked(loadSessionSnapshot).mockResolvedValue(
556
+ replaceSessionSnapshot(createEmptySessionSnapshot(), {
557
+ runningTurn,
558
+ unstable_resume: true,
559
+ }),
560
+ );
561
+ const serverWithoutSubscribe = {
562
+ cancelSession: vi.fn().mockResolvedValue(undefined),
563
+ listTurns: vi.fn().mockResolvedValue({ data: [] }),
564
+ } as unknown as AgentChatServer;
565
+
566
+ const { result } = renderHook(() =>
567
+ useTrueFoundryAgentMessages({
568
+ server: serverWithoutSubscribe,
569
+ sessionId: "session-1",
570
+ onError: vi.fn(),
571
+ }),
572
+ );
573
+
574
+ await waitFor(() => expect(result.current.isRunning).toBe(true));
575
+
576
+ await act(async () => {
577
+ await result.current.cancel();
578
+ });
579
+
580
+ expect(serverWithoutSubscribe.cancelSession).toHaveBeenCalledWith({
581
+ sessionId: "session-1",
582
+ });
583
+ // No stream was attached, so nothing else would release the composer.
584
+ expect(result.current.isRunning).toBe(false);
585
+ expect(result.current.resumeUnavailable).toBe(false);
586
+ });
587
+
588
+ it("stays in the waiting state instead of resuming when resumeRun has no subscribeToTurn", async () => {
589
+ const onError = vi.fn();
590
+ const runningTurn = { id: "turn-running" } as Turn;
591
+ vi.mocked(loadSessionSnapshot).mockResolvedValue(
592
+ replaceSessionSnapshot(createEmptySessionSnapshot(), {
593
+ runningTurn,
594
+ unstable_resume: true,
595
+ }),
596
+ );
597
+ const serverWithoutSubscribe = {
598
+ cancelSession: vi.fn().mockResolvedValue(undefined),
599
+ listTurns: vi.fn().mockResolvedValue({ data: [] }),
600
+ } as unknown as AgentChatServer;
601
+
602
+ const { result } = renderHook(() =>
603
+ useTrueFoundryAgentMessages({
604
+ server: serverWithoutSubscribe,
605
+ sessionId: "session-1",
606
+ onError,
607
+ }),
608
+ );
609
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
610
+ onError.mockClear();
611
+
612
+ await act(async () => {
613
+ await result.current.resumeRun();
614
+ });
615
+
616
+ expect(resumeTurnStream).not.toHaveBeenCalled();
617
+ expect(result.current.resumeUnavailable).toBe(true);
618
+ expect(onError).not.toHaveBeenCalled();
619
+ });
620
+
506
621
  it("clears isLoading while a resumed turn is still streaming", async () => {
507
622
  let releaseStream: (() => void) | undefined;
508
623
  vi.mocked(resumeTurnStream).mockReturnValue(
@@ -309,6 +309,7 @@ export function useTrueFoundryAgentMessages({
309
309
  );
310
310
  const [isLoadingOlderHistory, setIsLoadingOlderHistory] = useState(false);
311
311
  const [loadRetryTrigger, setLoadRetryTrigger] = useState(0);
312
+ const [resumeUnavailable, setResumeUnavailable] = useState(false);
312
313
 
313
314
  const snapshotRef = useRef(snapshot);
314
315
  snapshotRef.current = snapshot;
@@ -326,6 +327,8 @@ export function useTrueFoundryAgentMessages({
326
327
  const createdAtByMessageIdRef = useRef(new Map<string, Date>());
327
328
  const abortControllerRef = useRef<AbortController | null>(null);
328
329
  const activeRunRef = useRef<Promise<void> | null>(null);
330
+ // Mirrors `resumeUnavailable` for `cancel`, which reads it outside render.
331
+ const resumeUnavailableRef = useRef(false);
329
332
  const runningTurnRef = useRef<Turn | undefined>(undefined);
330
333
  const loadGenerationRef = useRef(0);
331
334
  const streamGenerationRef = useRef(0);
@@ -333,6 +336,16 @@ export function useTrueFoundryAgentMessages({
333
336
  const initialLoadStartedForRef = useRef<string | undefined>(undefined);
334
337
  const skipInitialPromotionLoadForRef = useRef<string | undefined>(undefined);
335
338
 
339
+ /**
340
+ * A turn is running that this server cannot stream. Nothing will deliver its
341
+ * result to this client, so the UI shows a waiting state until the run is
342
+ * cancelled or the session is reloaded.
343
+ */
344
+ const markResumeUnavailable = useCallback((value: boolean) => {
345
+ resumeUnavailableRef.current = value;
346
+ setResumeUnavailable(value);
347
+ }, []);
348
+
336
349
  const projectOptions = useMemo(
337
350
  () => ({
338
351
  getCreatedAt: (messageId: string, fallback: Date) => {
@@ -371,6 +384,8 @@ export function useTrueFoundryAgentMessages({
371
384
  const abortController = new AbortController();
372
385
  abortControllerRef.current = abortController;
373
386
  setIsRunning(true);
387
+ // This stream's `finally` owns the running flag from here on.
388
+ markResumeUnavailable(false);
374
389
 
375
390
  const run = (async () => {
376
391
  // Sub-agent turns can emit 100+ stream events per frame. Coalesce to one
@@ -516,6 +531,7 @@ export function useTrueFoundryAgentMessages({
516
531
  const generation = ++loadGenerationRef.current;
517
532
  ++streamGenerationRef.current;
518
533
  setIsRunning(false);
534
+ markResumeUnavailable(false);
519
535
  abortControllerRef.current?.abort();
520
536
  loadOlderInflightRef.current = null;
521
537
  createdAtByMessageIdRef.current = new Map();
@@ -554,6 +570,16 @@ export function useTrueFoundryAgentMessages({
554
570
 
555
571
  if (loadedSnapshot.runningTurn != null) {
556
572
  const turn = loadedSnapshot.runningTurn;
573
+
574
+ // subscribeToTurn is optional, so a server can leave us without
575
+ // a reconnect path. The turn still runs on the backend: show the
576
+ // loaded history as running and let the host explain the gap.
577
+ if (server.subscribeToTurn == null) {
578
+ setIsRunning(true);
579
+ markResumeUnavailable(true);
580
+ return;
581
+ }
582
+
557
583
  const isContinuation = !extractTurnUserText(turn.input);
558
584
  // TODO: pass afterSequenceNumber once stream ingestion tracks sequence numbers.
559
585
  // Use loadedSnapshot directly — snapshotRef.current still points at
@@ -831,7 +857,13 @@ export function useTrueFoundryAgentMessages({
831
857
  // reconcile is needed here — the cancelled turn is terminal and local
832
858
  // state reconciles against the event log on the next session load.
833
859
  await activeRunRef.current?.catch(() => undefined);
834
- }, [server, sessionId]);
860
+ // Nothing drained when the load could not attach a stream, so clear the
861
+ // running flag here or the composer stays blocked until a reload.
862
+ if (resumeUnavailableRef.current) {
863
+ markResumeUnavailable(false);
864
+ setIsRunning(false);
865
+ }
866
+ }, [server, sessionId, markResumeUnavailable]);
835
867
 
836
868
  const isRunningRef = useRef(isRunning);
837
869
  isRunningRef.current = isRunning;
@@ -897,6 +929,10 @@ export function useTrueFoundryAgentMessages({
897
929
  if (turn == null) {
898
930
  return;
899
931
  }
932
+ if (server.subscribeToTurn == null) {
933
+ markResumeUnavailable(true);
934
+ return;
935
+ }
900
936
  // TODO: pass afterSequenceNumber once stream ingestion tracks sequence numbers.
901
937
  await runStream(
902
938
  (signal) =>
@@ -1066,6 +1102,7 @@ export function useTrueFoundryAgentMessages({
1066
1102
  return {
1067
1103
  messages,
1068
1104
  isRunning,
1105
+ resumeUnavailable,
1069
1106
  isLoading,
1070
1107
  isLoadingOlderHistory,
1071
1108
  hasOlderHistory,
@@ -108,6 +108,7 @@ function useTrueFoundryAgentRuntimeImpl(
108
108
  const {
109
109
  messages,
110
110
  isRunning,
111
+ resumeUnavailable,
111
112
  isLoading,
112
113
  isLoadingOlderHistory,
113
114
  hasOlderHistory,
@@ -187,6 +188,8 @@ function useTrueFoundryAgentRuntimeImpl(
187
188
  isSpecSyncing: draftSpec.isSpecSyncing,
188
189
  specError: draftSpec.specError,
189
190
  updateAgentSpec: draftSpec.updateAgentSpec,
191
+ flushAgentSpec: draftSpec.flushAgentSpec,
192
+ adoptAgentSpec: draftSpec.adoptAgentSpec,
190
193
  };
191
194
  }, [agent.mode, draftSpec]);
192
195
 
@@ -199,6 +202,7 @@ function useTrueFoundryAgentRuntimeImpl(
199
202
  pendingApprovals,
200
203
  pendingToolResponses,
201
204
  pendingMcpAuth,
205
+ resumeUnavailable,
202
206
  sandboxId,
203
207
  respondToToolApproval,
204
208
  respondToToolResponse,
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/plugins/truefoundry-agent-server-adapter/chatServer.ts","../src/plugins/truefoundry-agent-server-adapter/normalizeAgentSpec.ts","../src/plugins/truefoundry-agent-server-adapter/cp.ts","../src/plugins/truefoundry-agent-server-adapter/createTrueFoundryAgentUIServer.ts","../src/plugins/truefoundry-agent-server-adapter/guards.ts"],"sourcesContent":["import { AgentSessionClient } from \"truefoundry-gateway-sdk/agents\";\nimport type { AgentSession } from \"truefoundry-gateway-sdk/agents\";\nimport { PrivateAgentSessionClient } from \"truefoundry-gateway-sdk/agents/private\";\nimport type { AgentDraftSession } from \"truefoundry-gateway-sdk/agents/private\";\nimport type {\n AgentChatServer,\n ListResult,\n TurnInputItem,\n PreviousTurnIdInput,\n UpdateSessionRequest,\n} from \"../../server/types.js\";\nimport type {\n SessionEventItem,\n TurnEvent,\n TurnStreamData,\n} from \"../../server/events.js\";\nimport { normalizeAgentSpecForGateway } from \"./normalizeAgentSpec.js\";\nimport type {\n TfyAgentSpec,\n TfyCreateSessionRequest,\n TfyListSessionsParams,\n TfySession,\n TfyTurn,\n TfyTurnState,\n} from \"./types.js\";\n\ntype GwSession = AgentSession | AgentDraftSession;\n\nexport type CreateTrueFoundryChatServerOptions = {\n apiKey: string;\n baseUrl: string;\n /** Optional override — otherwise constructed from apiKey/baseUrl. */\n client?: AgentSessionClient;\n privateClient?: PrivateAgentSessionClient;\n deleteSession?: (req: { sessionId: string }) => Promise<void>;\n};\n\n/**\n * Only the spec is generic. Session/Turn/list-params are the concrete Tfy*\n * types because the adapter builds them as fixed object literals — a generic\n * there would type fields that nothing ever populates. The spec is safe: the\n * gateway SDK serializes with `unrecognizedObjectKeys: \"passthrough\"`, so\n * host-added spec fields survive the round trip.\n */\nexport type TrueFoundryChatServer<TSpec extends TfyAgentSpec = TfyAgentSpec> =\n AgentChatServer<\n TSpec,\n TfySession<TSpec>,\n TfyCreateSessionRequest<TSpec>,\n TfyListSessionsParams,\n UpdateSessionRequest<TSpec>,\n TfyTurn\n > & {\n /** Escape hatch for hosts that still need raw gateway clients. */\n getGatewayClients(): {\n client: AgentSessionClient;\n privateClient: PrivateAgentSessionClient;\n };\n };\n\n/** Gateway SDK errors carry the HTTP status as `statusCode`. */\nfunction isNotFound(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as { statusCode?: unknown }).statusCode === 404\n );\n}\n\nfunction isDraft(session: GwSession): session is AgentDraftSession {\n return (session as AgentDraftSession).type === \"session/draft\";\n}\n\nfunction toSession<TSpec extends TfyAgentSpec>(raw: GwSession): TfySession<TSpec> {\n const mutable = isDraft(raw);\n return {\n id: raw.id,\n title: raw.title,\n agentName: raw.agentName,\n ...(mutable ? { agentSpec: raw.agentSpec as TSpec } : {}),\n isMutable: mutable,\n createdBySubject: raw.createdBySubject,\n createdAt: raw.createdAt,\n updatedAt: raw.updatedAt,\n };\n}\n\nfunction toTurn(raw: {\n id: string;\n sessionId: string;\n previousTurnId?: string | null;\n input?: TurnInputItem[];\n state: unknown;\n createdBySubject: TfyTurn[\"createdBySubject\"];\n createdAt: string;\n}): TfyTurn {\n return {\n id: raw.id,\n sessionId: raw.sessionId,\n previousTurnId: raw.previousTurnId,\n input: raw.input,\n state: raw.state as TfyTurnState,\n createdBySubject: raw.createdBySubject,\n createdAt: raw.createdAt,\n };\n}\n\nasync function toListResult<TIn, TOut>(\n page: {\n data: TIn[];\n response?: { pagination?: { nextPageToken?: string } };\n hasNextPage?: () => boolean;\n },\n map: (item: TIn) => TOut,\n): Promise<ListResult<TOut>> {\n const nextPageToken = page.response?.pagination?.nextPageToken;\n return {\n data: page.data.map(map),\n ...(nextPageToken != null && nextPageToken !== \"\"\n ? { nextPageToken }\n : {}),\n };\n}\n\n/**\n * Wraps TrueFoundry gateway clients into a flat `AgentChatServer`.\n * Named vs draft routing is fully internal — an in-memory session-type cache\n * (populated by createSession/listSessions) determines which gateway client\n * to call, falling back to a one-time probe for ids seen only in a URL.\n */\nexport function createTrueFoundryChatServer<\n TSpec extends TfyAgentSpec = TfyAgentSpec,\n>(\n opts: CreateTrueFoundryChatServerOptions,\n): TrueFoundryChatServer<TSpec> {\n const gatewayOpts = { apiKey: opts.apiKey, baseUrl: opts.baseUrl };\n const client = opts.client ?? new AgentSessionClient(gatewayOpts);\n const privateClient =\n opts.privateClient ?? new PrivateAgentSessionClient(gatewayOpts);\n\n const sessionTypeCache = new Map<string, boolean>();\n const sessionTypeProbes = new Map<string, Promise<boolean>>();\n\n function cacheSessionType(session: {\n id: string;\n isMutable: boolean;\n }): void {\n sessionTypeCache.set(session.id, session.isMutable);\n }\n\n /**\n * Resolving a session id straight from a URL (page reload / shared link)\n * reaches the gateway before createSession or listSessions has cached the\n * type, so discover it by probing the draft endpoint and falling back to\n * the conversation endpoint. Concurrent callers share one probe.\n */\n async function probeSessionType(sessionId: string): Promise<boolean> {\n const inflight = sessionTypeProbes.get(sessionId);\n if (inflight != null) {\n return inflight;\n }\n const probe = (async () => {\n try {\n await privateClient.getDraftSession({ draftSessionId: sessionId });\n return true;\n } catch (error) {\n // Only a miss proves the id isn't a draft. Auth or transient\n // failures would otherwise be retried as a conversation session\n // and mislabel a real draft as named.\n if (!isNotFound(error)) {\n throw error;\n }\n await client.getSession({ sessionId });\n return false;\n }\n })();\n sessionTypeProbes.set(sessionId, probe);\n try {\n const isMutable = await probe;\n sessionTypeCache.set(sessionId, isMutable);\n return isMutable;\n } finally {\n sessionTypeProbes.delete(sessionId);\n }\n }\n\n async function getSessionObj(sessionId: string): Promise<GwSession> {\n const isMutable =\n sessionTypeCache.get(sessionId) ?? (await probeSessionType(sessionId));\n return isMutable\n ? privateClient.getDraftSession({ draftSessionId: sessionId })\n : client.getSession({ sessionId });\n }\n\n const server: TrueFoundryChatServer<TSpec> = {\n async createSession(req) {\n if (req.agentSpec != null) {\n const draft = await privateClient.createDraftSession({\n agentSpec: normalizeAgentSpecForGateway(req.agentSpec),\n ...(req.agentName != null ? { agentName: req.agentName } : {}),\n ...(req.tfyMetadata != null\n ? { tfyMetadata: req.tfyMetadata }\n : {}),\n });\n const session = toSession<TSpec>(draft);\n cacheSessionType(session);\n return session;\n }\n if (req.agentName != null) {\n const named = await client.createSession({\n agentName: req.agentName,\n ...(req.tfyMetadata != null\n ? { tfyMetadata: req.tfyMetadata }\n : {}),\n });\n const session = toSession<TSpec>(named);\n cacheSessionType(session);\n return session;\n }\n throw new Error(\"createSession requires agentName and/or agentSpec\");\n },\n\n async listSessions(req) {\n const page = await privateClient.listOwnedSessions({\n limit: req?.limit,\n order: req?.order,\n pageToken: req?.pageToken,\n startTimestamp: req?.startTimestamp,\n endTimestamp: req?.endTimestamp,\n ...(req?.agentId != null ? { agentName: req.agentId } : {}),\n });\n const result = await toListResult(page, (s) => toSession<TSpec>(s));\n for (const session of result.data) {\n cacheSessionType(session);\n }\n return result;\n },\n\n async getSession({ sessionId }) {\n const raw = await getSessionObj(sessionId);\n const session = toSession<TSpec>(raw);\n cacheSessionType(session);\n return session;\n },\n\n async updateSession(req) {\n const raw = await getSessionObj(req.sessionId);\n if (!isDraft(raw)) {\n throw new Error(\n \"updateSession: session is not mutable (isMutable=false)\",\n );\n }\n if (req.agentSpec != null) {\n await raw.update({\n agentSpec: normalizeAgentSpecForGateway(req.agentSpec),\n });\n }\n return toSession<TSpec>(raw);\n },\n\n createTurn(req: {\n sessionId: string;\n input?: TurnInputItem[];\n previousTurnId?: PreviousTurnIdInput;\n abortSignal?: AbortSignal;\n headers?: Record<string, string>;\n }): AsyncIterable<TurnStreamData> {\n return (async function* () {\n const session = await getSessionObj(req.sessionId);\n const prepared = session.prepareTurn({\n input: req.input,\n previousTurnId: req.previousTurnId ?? \"auto\",\n });\n yield* prepared.execute(\n { stream: true },\n {\n ...(req.abortSignal != null\n ? { abortSignal: req.abortSignal }\n : {}),\n ...(req.headers != null ? { headers: req.headers } : {}),\n },\n ) as AsyncIterable<TurnStreamData>;\n })();\n },\n\n async cancelSession({ sessionId }) {\n await (await getSessionObj(sessionId)).cancel();\n },\n\n async deleteSession({ sessionId }) {\n if (opts.deleteSession == null) {\n throw new Error(\n \"deleteSession is not on the gateway SDK. Pass deleteSession to createTrueFoundryChatServer.\",\n );\n }\n await opts.deleteSession({ sessionId });\n },\n\n // The runtime's signature offers `order`, but the gateway's listTurns\n // takes no such param — forwarding it silently did nothing.\n async listTurns({ sessionId, limit, pageToken }) {\n const raw = await getSessionObj(sessionId);\n const page = await raw.listTurns({\n ...(limit != null ? { limit } : {}),\n ...(pageToken != null ? { pageToken } : {}),\n });\n return toListResult(page, (turn) => toTurn(turn));\n },\n\n async getTurn({ sessionId, turnId }) {\n const raw = await getSessionObj(sessionId);\n return toTurn(await raw.getTurn({ turnId }));\n },\n\n async listEvents({ sessionId, pageToken, lastTurnId, limit }) {\n const raw = await getSessionObj(sessionId);\n const page = await raw.listEvents({\n ...(limit != null ? { limit } : {}),\n ...(pageToken != null ? { pageToken } : {}),\n ...(lastTurnId != null ? { lastTurnId } : {}),\n });\n return toListResult(\n page,\n (item) => item as SessionEventItem,\n );\n },\n\n async listTurnEvents({ sessionId, turnId, limit, pageToken, order }) {\n const raw = await getSessionObj(sessionId);\n const turn = await raw.getTurn({ turnId });\n const page = await turn.listEvents({\n ...(limit != null ? { limit } : {}),\n ...(pageToken != null ? { pageToken } : {}),\n ...(order != null ? { order } : {}),\n });\n return toListResult(page, (event) => event as TurnEvent);\n },\n\n async *subscribeToTurn({\n sessionId,\n turnId,\n afterSequenceNumber,\n abortSignal,\n }) {\n const raw = await getSessionObj(sessionId);\n const turn = await raw.getTurn({ turnId });\n yield* turn.stream(\n afterSequenceNumber != null ? { afterSequenceNumber } : {},\n abortSignal != null ? { abortSignal } : {},\n ) as AsyncIterable<TurnStreamData>;\n },\n\n // The gateway addresses sandboxes directly, so the turn is carried for hosts that need it\n // but is not part of this route.\n async downloadSandboxFile({ sandboxId, path }) {\n const response = await privateClient.downloadSandboxFile(sandboxId, {\n path,\n });\n return await response.blob();\n },\n\n getGatewayClients: () => ({ client, privateClient }),\n };\n\n return server;\n}\n","import type { TruefoundryGatewayApi } from \"truefoundry-gateway-sdk\";\n\nimport type { TfyAgentSpec } from \"./types.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/**\n * agent-ui-sdk DraftCompositeSelector writes catalog rows as `{ id, name }`.\n * Gateway AgentSpec mounts need discriminated registry shapes — without\n * `type` (and skill `fqn`), Fern serialization throws JsonError before PATCH.\n *\n * Our CP catalog sets skill `id` = version fqn and MCP `id` = server name.\n */\nexport function normalizeMcpMount(\n raw: unknown,\n): TruefoundryGatewayApi.McpServer {\n if (!isRecord(raw)) {\n throw new Error(\"mcpServers entry must be an object\");\n }\n if (raw.type === \"truefoundry-mcp-registry\" || raw.type === \"inline\") {\n return raw as unknown as TruefoundryGatewayApi.McpServer;\n }\n const name =\n (typeof raw.name === \"string\" && raw.name !== \"\" ? raw.name : null) ??\n (typeof raw.mcpName === \"string\" && raw.mcpName !== \"\"\n ? raw.mcpName\n : null) ??\n (typeof raw.id === \"string\" && raw.id !== \"\" ? raw.id : null);\n if (name == null) {\n throw new Error(\n \"mcpServers entry needs name, mcpName, or id to mount as registry MCP\",\n );\n }\n return {\n type: \"truefoundry-mcp-registry\",\n name,\n ...(Array.isArray(raw.enableTools) ? { enableTools: raw.enableTools } : {}),\n ...(typeof raw.preload === \"boolean\" ? { preload: raw.preload } : {}),\n ...(raw.config != null ? { config: raw.config } : {}),\n } as unknown as TruefoundryGatewayApi.McpServer;\n}\n\nexport function normalizeSkillMount(\n raw: unknown,\n): TruefoundryGatewayApi.SkillMount {\n if (!isRecord(raw)) {\n throw new Error(\"skills entry must be an object\");\n }\n if (raw.type === \"truefoundry-skills-registry\" || raw.type === \"git\") {\n return raw as unknown as TruefoundryGatewayApi.SkillMount;\n }\n const fqn =\n (typeof raw.fqn === \"string\" && raw.fqn !== \"\" ? raw.fqn : null) ??\n (typeof raw.id === \"string\" && raw.id !== \"\" ? raw.id : null);\n if (fqn == null) {\n throw new Error(\n \"skills entry needs fqn or id to mount as registry skill\",\n );\n }\n return {\n type: \"truefoundry-skills-registry\",\n fqn,\n ...(typeof raw.preload === \"boolean\" ? { preload: raw.preload } : {}),\n ...(raw.config != null ? { config: raw.config } : {}),\n } as unknown as TruefoundryGatewayApi.SkillMount;\n}\n\n/** Rewrite UI `{id,name}` mounts so create/update draft session can serialize. */\nexport function normalizeAgentSpecForGateway<TSpec extends TfyAgentSpec>(\n spec: TSpec,\n): TSpec {\n return {\n ...spec,\n ...(spec.mcpServers != null\n ? {\n mcpServers: spec.mcpServers.map(\n normalizeMcpMount,\n ) as TSpec[\"mcpServers\"],\n }\n : {}),\n ...(spec.skills != null\n ? {\n skills: spec.skills.map(\n normalizeSkillMount,\n ) as TSpec[\"skills\"],\n }\n : {}),\n };\n}\n","/**\n * Control Plane HTTP for builder catalog lists + gateway URL resolution.\n *\n * Wire shapes are host/CP contracts (not Fern / gateway SDK). Paths match\n * ai.tf cpApi usage — expect drift; keep normalizers defensive.\n */\n\nimport type {\n AgentSelectorEntry,\n ConnectorSelectorEntry,\n ModelSelectorEntry,\n SearchAgentSelectorParams,\n SkillSelectorEntry,\n} from \"../../server/types.js\";\nimport { normalizeAgentSpecForGateway } from \"./normalizeAgentSpec.js\";\nimport type { TfyAgentSpec } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Selector rows — FE base + TFY mount fields\n// ---------------------------------------------------------------------------\n\nexport interface TfyModelSelectorEntry extends ModelSelectorEntry {\n /** Write into AgentSpec.model.name (model_fqn). */\n apiModel: string;\n modelId: string;\n providerAccount?: string;\n}\n\nexport interface TfySkillSelectorEntry extends SkillSelectorEntry {\n /** Version FQN — mount as RegisteredSkillMount.fqn. */\n fqn: string;\n}\n\nexport interface TfyConnectorSelectorEntry extends ConnectorSelectorEntry {\n /** Mount as RegisteredMcpServer.name. */\n mcpName: string;\n serverId?: string | null;\n authenticated?: boolean;\n}\n\nexport type TfyAgentSelectorEntry = AgentSelectorEntry;\n\nexport type CpCredentials = {\n apiKey: string;\n cpURL: string;\n};\n\n// ---------------------------------------------------------------------------\n// HTTP\n// ---------------------------------------------------------------------------\n\nfunction trimSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nexport async function cpFetch<T>(\n opts: CpCredentials,\n path: string,\n init?: RequestInit,\n): Promise<T> {\n const url = `${trimSlash(opts.cpURL)}${path.startsWith(\"/\") ? path : `/${path}`}`;\n const res = await fetch(url, {\n ...init,\n headers: {\n Authorization: `Bearer ${opts.apiKey}`,\n Accept: \"application/json\",\n ...(init?.headers ?? {}),\n },\n });\n if (!res.ok) {\n throw new Error(`CP ${res.status} ${res.statusText}: ${path}`);\n }\n return (await res.json()) as T;\n}\n\n// ---------------------------------------------------------------------------\n// Gateway URL resolve\n// ---------------------------------------------------------------------------\n\n/**\n * Real CP `/api/svc/v1/session` is flat `{ user, env, ... }` (no `data` wrapper).\n * `env.LLM_GATEWAY_URL` is a prefix like `/api/llm`; tenant is appended:\n * `{cpURL}{LLM_GATEWAY_URL}/{tenantName}` → e.g. `…/api/llm/truefoundry`.\n */\ntype SessionResponse = {\n user?: {\n tenantName?: string;\n };\n env?: {\n TENANT_NAME?: string;\n LLM_GATEWAY_URL?: string;\n };\n /** Legacy / alternate wrap — keep for safety. */\n data?: {\n user?: {\n tenantName?: string;\n };\n };\n};\n\nfunction joinCpPath(cpURL: string, path: string): string {\n const base = trimSlash(cpURL);\n const p = path.startsWith(\"/\") ? path : `/${path}`;\n return `${base}${p}`;\n}\n\n/**\n * Resolve gateway base URL.\n * - `gatewayURL` set → use it (no /session).\n * - else GET /api/svc/v1/session →\n * `{cpURL}{env.LLM_GATEWAY_URL ?? \"/api/llm\"}/{tenantName}`.\n * - session HTTP failure or missing tenantName → throw (no silent public fallback).\n */\nexport async function resolveGatewayURL(opts: {\n apiKey: string;\n cpURL: string;\n gatewayURL?: string;\n}): Promise<string> {\n if (opts.gatewayURL != null && opts.gatewayURL !== \"\") {\n return opts.gatewayURL;\n }\n\n const session = await cpFetch<SessionResponse>(opts, \"/api/svc/v1/session\");\n const tenantName =\n session.user?.tenantName ??\n session.env?.TENANT_NAME ??\n session.data?.user?.tenantName;\n if (tenantName == null || tenantName === \"\") {\n throw new Error(\n \"resolveGatewayURL: /api/svc/v1/session did not return user.tenantName\",\n );\n }\n\n const llmPrefix = trimSlash(session.env?.LLM_GATEWAY_URL ?? \"/api/llm\");\n return joinCpPath(opts.cpURL, `${llmPrefix}/${tenantName}`);\n}\n\n// ---------------------------------------------------------------------------\n// Models\n// ---------------------------------------------------------------------------\n\ntype RawEnabledModel = {\n id?: string;\n name?: string;\n provider?: string;\n provider_account_name?: string;\n model_id?: string;\n model_fqn?: string;\n types?: string[];\n};\n\nfunction isChatModel(row: RawEnabledModel): boolean {\n const types = row.types;\n if (types == null || types.length === 0) return true;\n return types.includes(\"chat\");\n}\n\nfunction toModelEntry(row: RawEnabledModel): TfyModelSelectorEntry | null {\n if (!isChatModel(row)) return null;\n const apiModel = row.model_fqn ?? row.id;\n const name = row.name;\n if (apiModel == null || apiModel === \"\" || name == null || name === \"\") {\n return null;\n }\n const modelId = row.model_id ?? name;\n return {\n id: apiModel,\n name,\n provider: { name: row.provider ?? \"unknown\" },\n properties: {},\n apiModel,\n modelId,\n ...(row.provider_account_name != null\n ? { providerAccount: row.provider_account_name }\n : {}),\n };\n}\n\n/**\n * Flatten enabled-models payload.\n * Nested: provider → account → models[].\n * Virtual: top-level account → models[] (array values that look like model rows).\n */\nexport function normalizeEnabledModels(raw: unknown): TfyModelSelectorEntry[] {\n if (raw == null || typeof raw !== \"object\" || Array.isArray(raw)) {\n return [];\n }\n const out: TfyModelSelectorEntry[] = [];\n const seen = new Set<string>();\n\n function pushRow(row: RawEnabledModel): void {\n const entry = toModelEntry(row);\n if (entry == null || seen.has(entry.apiModel)) return;\n seen.add(entry.apiModel);\n out.push(entry);\n }\n\n for (const level1 of Object.values(raw as Record<string, unknown>)) {\n if (Array.isArray(level1)) {\n // virtual-model style: account → models[]\n for (const row of level1) {\n if (row != null && typeof row === \"object\") {\n pushRow(row as RawEnabledModel);\n }\n }\n continue;\n }\n if (level1 == null || typeof level1 !== \"object\") continue;\n for (const level2 of Object.values(level1 as Record<string, unknown>)) {\n if (!Array.isArray(level2)) continue;\n for (const row of level2) {\n if (row != null && typeof row === \"object\") {\n pushRow(row as RawEnabledModel);\n }\n }\n }\n }\n return out;\n}\n\nexport async function listEnabledModels(\n opts: CpCredentials,\n): Promise<TfyModelSelectorEntry[]> {\n const raw = await cpFetch<unknown>(\n opts,\n \"/api/svc/v1/llm-gateway/model/enabled\",\n );\n return normalizeEnabledModels(raw);\n}\n\n// ---------------------------------------------------------------------------\n// Skills\n// ---------------------------------------------------------------------------\n\ntype RawAgentSkill = {\n id?: string;\n name?: string;\n fqn?: string;\n latest_version?: {\n id?: string;\n fqn?: string;\n manifest?: {\n ml_repo?: string;\n source?: { description?: string };\n };\n };\n};\n\nexport function normalizeAgentSkills(raw: unknown): TfySkillSelectorEntry[] {\n const data =\n raw != null &&\n typeof raw === \"object\" &&\n Array.isArray((raw as { data?: unknown }).data)\n ? ((raw as { data: RawAgentSkill[] }).data)\n : [];\n const out: TfySkillSelectorEntry[] = [];\n for (const row of data) {\n const fqn = row.latest_version?.fqn;\n const name = row.name;\n if (fqn == null || fqn === \"\" || name == null || name === \"\") continue;\n const description = row.latest_version?.manifest?.source?.description;\n out.push({\n id: fqn,\n name,\n fqn,\n ...(description != null ? { description } : {}),\n });\n }\n return out;\n}\n\nexport async function listAgentSkills(\n opts: CpCredentials,\n): Promise<TfySkillSelectorEntry[]> {\n const raw = await cpFetch<unknown>(\n opts,\n \"/api/ml/v1/agent-skills?include_empty_agent_skills=false\",\n );\n return normalizeAgentSkills(raw);\n}\n\n// ---------------------------------------------------------------------------\n// MCP\n// ---------------------------------------------------------------------------\n\ntype RawMcpServer = {\n id?: string;\n name?: string;\n fqn?: string;\n manifest?: {\n description?: string;\n url?: string;\n auth_data?: { type?: string; auth_level?: string };\n };\n authStatus?: { status?: string };\n};\n\nexport function normalizeMcpServers(raw: unknown): TfyConnectorSelectorEntry[] {\n const data =\n raw != null &&\n typeof raw === \"object\" &&\n Array.isArray((raw as { data?: unknown }).data)\n ? ((raw as { data: RawMcpServer[] }).data)\n : [];\n const out: TfyConnectorSelectorEntry[] = [];\n const seen = new Set<string>();\n for (const row of data) {\n const mcpName = row.name;\n if (mcpName == null || mcpName === \"\" || seen.has(mcpName)) continue;\n seen.add(mcpName);\n const description = row.manifest?.description;\n const authenticated = row.authStatus?.status === \"authenticated\";\n out.push({\n id: mcpName,\n name: mcpName,\n mcpName,\n ...(description != null ? { description } : {}),\n ...(row.id != null ? { serverId: row.id } : { serverId: null }),\n authenticated,\n });\n }\n return out;\n}\n\nexport async function listMcpServers(\n opts: CpCredentials,\n): Promise<TfyConnectorSelectorEntry[]> {\n const raw = await cpFetch<unknown>(opts, \"/api/svc/v1/mcp-servers\");\n return normalizeMcpServers(raw);\n}\n\n// ---------------------------------------------------------------------------\n// Agents\n// ---------------------------------------------------------------------------\n\ntype RawAgent = {\n id?: string;\n name?: string;\n latestVersionDetails?: {\n manifest?: unknown;\n };\n /** Defensive: some payloads nest manifest at the top level. */\n manifest?: unknown;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value != null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction snakeToCamelKey(key: string): string {\n return key.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());\n}\n\n/** Deep snake_case → camelCase (inverse of {@link toSnakeCaseDeep}). */\nexport function toCamelCaseDeep(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(toCamelCaseDeep);\n }\n if (isRecord(value)) {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n out[snakeToCamelKey(k)] = toCamelCaseDeep(v);\n }\n return out;\n }\n return value;\n}\n\n/**\n * Map a CP AgentManifest (snake_case wire) → FE AgentSpec for Edit seeding.\n * Skills/MCP keep `{ id, name }` for draft pickers plus runtime fields\n * (`enableTools`, `preload`, `config`) so Edit → Save does not widen tool\n * access or wipe settings. Gateway registry shapes are restored on save via\n * normalizeAgentSpecForGateway.\n */\nexport function agentSpecFromCpManifest(manifest: unknown): TfyAgentSpec | undefined {\n if (!isRecord(manifest)) return undefined;\n const modelRaw = manifest.model;\n if (!isRecord(modelRaw) || typeof modelRaw.name !== \"string\" || modelRaw.name === \"\") {\n return undefined;\n }\n const model = toCamelCaseDeep(modelRaw) as TfyAgentSpec[\"model\"];\n\n // Catalog-shaped mounts (`id`/`name` + runtime fields). Not yet gateway\n // registry unions — normalizeAgentSpecForGateway restores those on save.\n const skills: Array<Record<string, unknown>> = [];\n if (Array.isArray(manifest.skills)) {\n for (const row of manifest.skills) {\n if (!isRecord(row)) continue;\n const fqn =\n typeof row.fqn === \"string\" && row.fqn !== \"\"\n ? row.fqn\n : typeof row.id === \"string\" && row.id !== \"\"\n ? row.id\n : null;\n if (fqn == null) continue;\n const camel = toCamelCaseDeep(row) as Record<string, unknown>;\n const name =\n typeof camel.name === \"string\" && camel.name !== \"\"\n ? camel.name\n : fqn;\n skills.push({\n id: fqn,\n name,\n ...(typeof camel.preload === \"boolean\"\n ? { preload: camel.preload }\n : {}),\n ...(camel.config != null ? { config: camel.config } : {}),\n });\n }\n }\n\n const mcpServers: Array<Record<string, unknown>> = [];\n const mcpRaw = Array.isArray(manifest.mcp_servers)\n ? manifest.mcp_servers\n : Array.isArray(manifest.mcpServers)\n ? manifest.mcpServers\n : [];\n for (const row of mcpRaw) {\n if (!isRecord(row)) continue;\n const camel = toCamelCaseDeep(row) as Record<string, unknown>;\n const name =\n typeof camel.name === \"string\" && camel.name !== \"\"\n ? camel.name\n : typeof camel.id === \"string\" && camel.id !== \"\"\n ? camel.id\n : null;\n if (name == null) continue;\n mcpServers.push({\n id: name,\n name,\n ...(Array.isArray(camel.enableTools)\n ? { enableTools: camel.enableTools }\n : {}),\n ...(typeof camel.preload === \"boolean\"\n ? { preload: camel.preload }\n : {}),\n ...(camel.config != null ? { config: camel.config } : {}),\n });\n }\n\n const configRaw = manifest.config;\n const config =\n configRaw != null ? (toCamelCaseDeep(configRaw) as TfyAgentSpec[\"config\"]) : undefined;\n\n return {\n model,\n ...(typeof manifest.instructions === \"string\"\n ? { instructions: manifest.instructions }\n : {}),\n ...(config != null ? { config } : {}),\n ...(skills.length > 0 ? { skills } : {}),\n ...(mcpServers.length > 0 ? { mcpServers } : {}),\n } as TfyAgentSpec;\n}\n\nexport function normalizeAgents(raw: unknown): TfyAgentSelectorEntry[] {\n const data =\n raw != null &&\n typeof raw === \"object\" &&\n Array.isArray((raw as { data?: unknown }).data)\n ? ((raw as { data: RawAgent[] }).data)\n : [];\n const out: TfyAgentSelectorEntry[] = [];\n for (const row of data) {\n if (row.name == null || row.name === \"\") continue;\n const manifest = row.latestVersionDetails?.manifest ?? row.manifest;\n const agentSpec = agentSpecFromCpManifest(manifest);\n const agentId =\n typeof row.id === \"string\" && row.id !== \"\" ? row.id : row.name;\n out.push({\n name: row.name,\n agentId,\n ...(agentSpec != null ? { agentSpec } : {}),\n });\n }\n return out;\n}\n\nexport async function listAgents(\n opts: CpCredentials,\n req?: SearchAgentSelectorParams,\n): Promise<TfyAgentSelectorEntry[]> {\n const limit = req?.limit ?? 50;\n const offset = req?.offset ?? 0;\n const namePrefix = req?.query ?? \"\";\n const qs = new URLSearchParams({\n type: \"truefoundry-agent\",\n limit: String(limit),\n offset: String(offset),\n namePrefix,\n });\n const raw = await cpFetch<unknown>(\n opts,\n `/api/svc/v1/agents?${qs.toString()}`,\n );\n return normalizeAgents(raw);\n}\n\n// ---------------------------------------------------------------------------\n// Save agent (PUT upsert by name)\n// ---------------------------------------------------------------------------\n\n/** Platform feature flags baked into every saved agent manifest. */\nexport const SAVE_AGENT_METADATA_TAGS = {\n agent: \"tfy-ai-gateway-agent\",\n TFY_ALPHA_ENABLE_OPENUI: \"true\",\n TFY_ALPHA_ENABLE_ASK_USER: \"true\",\n TFY_ALPHA_ENABLE_ASK_SECRET: \"true\",\n TFY_ALPHA_CONTEXT_MANAGEMENT:\n '{\"large_tool_response\":{\"individual_tool_response_token_threshold\":8000}}',\n TFY_ALPHA_ENABLE_FILE_DOWNLOAD: \"true\",\n} as const;\n\n/** Default ACL on create/update — everyone on the tenant gets agent-access. */\nexport const SAVE_AGENT_COLLABORATORS = [\n { subject: \"team:everyone\", role_id: \"agent-access\" },\n] as const;\n\nfunction camelToSnakeKey(key: string): string {\n return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/** Deep camelCase → snake_case for CP wire (model.params, config, mounts). */\nexport function toSnakeCaseDeep(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(toSnakeCaseDeep);\n }\n if (value != null && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[camelToSnakeKey(k)] = toSnakeCaseDeep(v);\n }\n return out;\n }\n return value;\n}\n\nfunction mcpMountForCp(mount: unknown): Record<string, unknown> {\n const snake = toSnakeCaseDeep(mount) as Record<string, unknown>;\n if (snake.type === \"truefoundry-mcp-registry\") {\n return {\n ...snake,\n enable_tools: snake.enable_tools ?? [\"@all\"],\n preload: snake.preload ?? false,\n };\n }\n return snake;\n}\n\nfunction skillMountForCp(mount: unknown): Record<string, unknown> {\n const snake = toSnakeCaseDeep(mount) as Record<string, unknown>;\n if (snake.type === \"truefoundry-skills-registry\") {\n return {\n ...snake,\n preload: snake.preload ?? false,\n };\n }\n return snake;\n}\n\n/**\n * Build CP `manifest` for `PUT /api/svc/v1/agents`.\n * Normalizes UI catalog mounts, snake_cases gateway fields, hardcodes type /\n * metadata_tags / collaborators.\n */\nexport function buildSaveAgentManifest(\n agentName: string,\n agentSpec: TfyAgentSpec,\n): Record<string, unknown> {\n const spec = normalizeAgentSpecForGateway(agentSpec);\n const mcpServers = (spec.mcpServers ?? []).map(mcpMountForCp);\n const skills = (spec.skills ?? []).map(skillMountForCp);\n // CP AgentManifest requires description; not on FE AgentSpec yet.\n const rawDescription = (agentSpec as { description?: unknown }).description;\n const description = typeof rawDescription === \"string\" ? rawDescription : \"\";\n\n return {\n type: \"truefoundry-agent\",\n name: agentName,\n description,\n model: toSnakeCaseDeep(spec.model),\n metadata_tags: { ...SAVE_AGENT_METADATA_TAGS },\n collaborators: [...SAVE_AGENT_COLLABORATORS],\n ...(spec.instructions != null ? { instructions: spec.instructions } : {}),\n ...(spec.config != null ? { config: toSnakeCaseDeep(spec.config) } : {}),\n ...(mcpServers.length > 0 ? { mcp_servers: mcpServers } : {}),\n ...(skills.length > 0 ? { skills } : {}),\n };\n}\n\n/**\n * Upsert a named agent on the Control Plane.\n * `PUT /api/svc/v1/agents` with `{ manifest }` — name is the upsert key.\n */\nexport async function saveAgent(\n opts: CpCredentials,\n req: { agentName: string; agentSpec: TfyAgentSpec },\n): Promise<unknown> {\n const manifest = buildSaveAgentManifest(req.agentName, req.agentSpec);\n return cpFetch(opts, \"/api/svc/v1/agents\", {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ manifest }),\n });\n}\n","import type { AgentBuilderServer } from \"../../server/types.js\";\nimport {\n listAgents,\n listAgentSkills,\n listEnabledModels,\n listMcpServers,\n resolveGatewayURL,\n saveAgent,\n type TfyAgentSelectorEntry,\n type TfyConnectorSelectorEntry,\n type TfyModelSelectorEntry,\n type TfySkillSelectorEntry,\n} from \"./cp.js\";\nimport {\n createTrueFoundryChatServer,\n type TrueFoundryChatServer,\n} from \"./chatServer.js\";\nimport type { TfyAgentSpec } from \"./types.js\";\n\nexport type CreateTrueFoundryAgentUIServerOptions = {\n apiKey: string;\n /** Control Plane base URL (builder lists + optional /session for gateway resolve). */\n cpURL: string;\n /**\n * Gateway base URL. When omitted, resolved via\n * `GET {cpURL}/api/svc/v1/session` → `{cpURL}{env.LLM_GATEWAY_URL ?? \"/api/llm\"}/{tenantName}`.\n * Session failure throws (no silent public-gateway fallback).\n */\n gatewayURL?: string;\n};\n\nexport type TrueFoundryAgentUIServer<TSpec extends TfyAgentSpec = TfyAgentSpec> =\n TrueFoundryChatServer<TSpec> &\n AgentBuilderServer<\n TSpec,\n TfyModelSelectorEntry,\n TfySkillSelectorEntry,\n TfyConnectorSelectorEntry,\n TfyAgentSelectorEntry,\n unknown\n >;\n\nfunction credentialsKey(opts: CreateTrueFoundryAgentUIServerOptions): string {\n return `${opts.apiKey}\\0${opts.cpURL}\\0${opts.gatewayURL ?? \"\"}`;\n}\n\nconst inflight = new Map<\n string,\n Promise<TrueFoundryAgentUIServer<TfyAgentSpec>>\n>();\n\n/**\n * Full pack: gateway chat + Control Plane builder lists.\n *\n * Same `apiKey` bearer is used for CP and gateway. Concurrent calls with the\n * same credentials share one in-flight promise (React Strict Mode safe).\n */\nexport async function createTrueFoundryAgentUIServer<\n TSpec extends TfyAgentSpec = TfyAgentSpec,\n>(\n opts: CreateTrueFoundryAgentUIServerOptions,\n): Promise<TrueFoundryAgentUIServer<TSpec>> {\n const key = credentialsKey(opts);\n const existing = inflight.get(key);\n if (existing != null) {\n return existing as Promise<TrueFoundryAgentUIServer<TSpec>>;\n }\n\n const promise = (async () => {\n const baseUrl = await resolveGatewayURL(opts);\n const chat = createTrueFoundryChatServer<TSpec>({\n apiKey: opts.apiKey,\n baseUrl,\n });\n const cp = { apiKey: opts.apiKey, cpURL: opts.cpURL };\n\n const server: TrueFoundryAgentUIServer<TSpec> = {\n ...chat,\n getCapabilities: async () => ({\n data: {\n sandbox: { enabled: true },\n skill: { enabled: true },\n settings: { enabled: true },\n },\n }),\n getModels: () => listEnabledModels(cp),\n getSkills: () => listAgentSkills(cp),\n getMcp: () => listMcpServers(cp),\n searchAgents: (req) => listAgents(cp, req),\n saveAgent: (req) => saveAgent(cp, req),\n };\n return server as TrueFoundryAgentUIServer<TfyAgentSpec>;\n })();\n\n inflight.set(key, promise);\n try {\n return (await promise) as TrueFoundryAgentUIServer<TSpec>;\n } finally {\n inflight.delete(key);\n }\n}\n","/**\n * Point-of-use narrowing for the event half of the gateway protocol.\n *\n * `AgentChatServer` hardcodes the runtime's event types on listEvents,\n * listTurnEvents, subscribeToTurn and createTurn — there is no\n * generic to override them from here. So instead of typing those channels,\n * hosts call these guards on the values they receive.\n *\n * They validate rather than cast: this data comes off the network, and the\n * runtime types the relevant fields as `unknown` precisely because nothing\n * has checked them yet.\n */\n\nimport type {\n TfyMcpServerInitInfo,\n TfyMcpToolInfo,\n TfyModelMessageUsage,\n TfySystemToolInfo,\n TfyThreadState,\n TfyToolInfo,\n} from \"./types.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction hasNumbers<K extends string>(\n value: unknown,\n keys: readonly K[],\n): value is Record<string, unknown> & Record<K, number> {\n return isRecord(value) && keys.every((key) => typeof value[key] === \"number\");\n}\n\n/**\n * Identifies built-in tools such as `ask_user_question` and `create_sub_agent`.\n * Note `toolInfo` is legitimately absent on streamed deltas, so callers must\n * keep their `function.name` fallback rather than treating absence as an error.\n */\nexport function isTfySystemToolInfo(\n toolInfo: unknown,\n): toolInfo is TfySystemToolInfo {\n return (\n isRecord(toolInfo) &&\n toolInfo.type === \"truefoundry-system\" &&\n typeof toolInfo.name === \"string\"\n );\n}\n\n/** Carries `serverId` / `serverName`, so the UI can attribute a call to its MCP server. */\nexport function isTfyMcpToolInfo(toolInfo: unknown): toolInfo is TfyMcpToolInfo {\n return (\n isRecord(toolInfo) &&\n toolInfo.type === \"mcp\" &&\n typeof toolInfo.name === \"string\" &&\n typeof toolInfo.serverId === \"string\" &&\n typeof toolInfo.serverName === \"string\"\n );\n}\n\nexport function isTfyToolInfo(toolInfo: unknown): toolInfo is TfyToolInfo {\n return isTfySystemToolInfo(toolInfo) || isTfyMcpToolInfo(toolInfo);\n}\n\nconst USAGE_BREAKDOWN_KEYS = [\n \"harness\",\n \"skills\",\n \"instructions\",\n \"toolDefinitions\",\n \"messages\",\n] as const;\n\n/**\n * Token usage including the TrueFoundry-specific `inputTokensBreakdown`, which\n * attributes input tokens across harness, skills, instructions, tool\n * definitions and messages.\n */\nexport function getTfyUsage(\n source: { usage?: unknown } | null | undefined,\n): TfyModelMessageUsage | undefined {\n const usage = source?.usage;\n if (!hasNumbers(usage, [\"inputTokens\", \"outputTokens\"])) {\n return undefined;\n }\n if (!hasNumbers(usage.inputTokensBreakdown, USAGE_BREAKDOWN_KEYS)) {\n return undefined;\n }\n return usage as unknown as TfyModelMessageUsage;\n}\n\n/**\n * Completion state of a sub-agent thread. The runtime types `thread.done`'s\n * `state` as `unknown`, so a sub-agent that errored is otherwise\n * indistinguishable from one that succeeded.\n */\nexport function getTfyThreadState(\n event: { state?: unknown } | null | undefined,\n): TfyThreadState | undefined {\n const state = event?.state;\n if (!isRecord(state)) {\n return undefined;\n }\n if (state.status === \"done\" && isRecord(state.output)) {\n return state as unknown as TfyThreadState;\n }\n if (state.status === \"error\" && typeof state.error === \"string\") {\n return state as unknown as TfyThreadState;\n }\n return undefined;\n}\n\n/**\n * Servers from an `mcp.initialize` event, including each one's `transportType`.\n * The runtime models this event with an index signature, so the array is\n * `unknown` until checked.\n */\nexport function getTfyMcpInitServers(\n event: { mcpServers?: unknown } | null | undefined,\n): TfyMcpServerInitInfo[] | undefined {\n const servers = event?.mcpServers;\n if (!Array.isArray(servers)) {\n return undefined;\n }\n const valid = servers.every(\n (server) =>\n isRecord(server) &&\n typeof server.id === \"string\" &&\n typeof server.name === \"string\",\n );\n return valid ? (servers as TfyMcpServerInitInfo[]) : undefined;\n}\n"],"mappings":"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","names":["inflight","isRecord","isRecord"]}