@truefoundry/assistant-ui-runtime 0.1.21 → 0.1.22

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.
@@ -19,7 +19,7 @@ import {
19
19
  reasoningEffortsForModel,
20
20
  } from "./modelReasoningEffort.js";
21
21
  import { normalizeAgentSpecForGateway } from "./normalizeAgentSpec.js";
22
- import type { TfyAgentSpec } from "./types.js";
22
+ import type { TfyAgentSpec, TfySaveAgentResult } from "./types.js";
23
23
 
24
24
  // ---------------------------------------------------------------------------
25
25
  // Selector rows — FE base + TFY mount fields
@@ -408,6 +408,61 @@ export function toCamelCaseDeep(value: unknown): unknown {
408
408
  return value;
409
409
  }
410
410
 
411
+ /** Keys are user data (e.g. metadata tag names) — copied verbatim, never case-converted. */
412
+ function stringRecordFromCp(raw: unknown): Record<string, string> | undefined {
413
+ if (!isRecord(raw)) return undefined;
414
+ const out: Record<string, string> = {};
415
+ for (const [key, value] of Object.entries(raw)) {
416
+ if (typeof value === "string") out[key] = value;
417
+ }
418
+ return Object.keys(out).length > 0 ? out : undefined;
419
+ }
420
+
421
+ /**
422
+ * Variable names are user keys and must survive verbatim. Values appear as
423
+ * plain strings in new manifests and `{ default_value }` records in older
424
+ * ones; both collapse to the resolved string.
425
+ */
426
+ function variablesFromCp(raw: unknown): Record<string, string> | undefined {
427
+ if (!isRecord(raw)) return undefined;
428
+ const out: Record<string, string> = {};
429
+ for (const [key, value] of Object.entries(raw)) {
430
+ if (typeof value === "string") {
431
+ out[key] = value;
432
+ } else if (isRecord(value) && typeof value.default_value === "string") {
433
+ out[key] = value.default_value;
434
+ }
435
+ }
436
+ return Object.keys(out).length > 0 ? out : undefined;
437
+ }
438
+
439
+ /**
440
+ * Rebuild the gateway ResponseFormat from the wire shape. Only the envelope
441
+ * key changes case (`json_schema` → `jsonSchema`); the `schema` body is a
442
+ * user-authored JSON schema whose property names must not be rewritten.
443
+ */
444
+ function responseFormatFromCp(raw: unknown): TfyAgentSpec["responseFormat"] {
445
+ if (!isRecord(raw)) return undefined;
446
+ if (raw.type === "text") return { type: "text" };
447
+ if (raw.type === "json_object") return { type: "json_object" };
448
+ if (raw.type !== "json_schema") return undefined;
449
+ const wireSchema = isRecord(raw.json_schema) ? raw.json_schema : undefined;
450
+ if (wireSchema == null || typeof wireSchema.name !== "string") return undefined;
451
+ return {
452
+ type: "json_schema",
453
+ jsonSchema: {
454
+ name: wireSchema.name,
455
+ ...(typeof wireSchema.description === "string"
456
+ ? { description: wireSchema.description }
457
+ : {}),
458
+ ...(isRecord(wireSchema.schema) ? { schema: wireSchema.schema } : {}),
459
+ ...(typeof wireSchema.strict === "boolean" || wireSchema.strict === null
460
+ ? { strict: wireSchema.strict }
461
+ : {}),
462
+ },
463
+ };
464
+ }
465
+
411
466
  /**
412
467
  * Map a CP AgentManifest (snake_case wire) → FE AgentSpec for Edit seeding.
413
468
  * Skills/MCP keep `{ id, name }` for draft pickers plus runtime fields
@@ -485,11 +540,39 @@ export function agentSpecFromCpManifest(manifest: unknown): TfyAgentSpec | undef
485
540
  const config =
486
541
  configRaw != null ? (toCamelCaseDeep(configRaw) as TfyAgentSpec["config"]) : undefined;
487
542
 
543
+ // Wire-named pass-through fields (description, messages, collaborators)
544
+ // need no mount remapping — one camelCase pass and a shape guard each.
545
+ // Guards drop malformed values instead of seeding the editor with garbage.
546
+ const camel = toCamelCaseDeep(manifest) as Record<string, unknown>;
547
+ const description =
548
+ typeof camel.description === "string" ? camel.description : undefined;
549
+ const messages = Array.isArray(camel.messages)
550
+ ? (camel.messages as TfyAgentSpec["messages"])
551
+ : undefined;
552
+ const collaborators = Array.isArray(camel.collaborators)
553
+ ? (camel.collaborators as TfyAgentSpec["collaborators"])
554
+ : undefined;
555
+
556
+ // User-keyed fields are read from the RAW manifest: their keys are data
557
+ // (tag names like "TFY_ALPHA_ENABLE_OPENUI", variable names like
558
+ // "my_city", JSON-schema property names), and case-converting them
559
+ // corrupts saved agents (e.g. "TFY_ALPHA…" → "tfyAlpha…" → on the next
560
+ // save "_t_f_y__a_l_p_h_a__…").
561
+ const variables = variablesFromCp(manifest.variables);
562
+ const tags = stringRecordFromCp(manifest.tags);
563
+ const responseFormat = responseFormatFromCp(manifest.response_format);
564
+
488
565
  return {
489
566
  model,
490
567
  ...(typeof manifest.instructions === "string"
491
568
  ? { instructions: manifest.instructions }
492
569
  : {}),
570
+ ...(description != null ? { description } : {}),
571
+ ...(variables != null ? { variables } : {}),
572
+ ...(messages != null ? { messages } : {}),
573
+ ...(responseFormat != null ? { responseFormat } : {}),
574
+ ...(tags != null ? { tags } : {}),
575
+ ...(collaborators != null ? { collaborators } : {}),
493
576
  ...(config != null ? { config } : {}),
494
577
  ...(skills.length > 0 ? { skills } : {}),
495
578
  ...(mcpServers.length > 0 ? { mcpServers } : {}),
@@ -544,7 +627,7 @@ export async function listAgents(
544
627
  // ---------------------------------------------------------------------------
545
628
 
546
629
  /** Platform feature flags baked into every saved agent manifest. */
547
- export const SAVE_AGENT_METADATA_TAGS = {
630
+ export const SAVE_AGENT_TAGS = {
548
631
  agent: "tfy-ai-gateway-agent",
549
632
  TFY_ALPHA_ENABLE_OPENUI: "true",
550
633
  TFY_ALPHA_ENABLE_ASK_USER: "true",
@@ -601,10 +684,47 @@ function skillMountForCp(mount: unknown): Record<string, unknown> {
601
684
  return snake;
602
685
  }
603
686
 
687
+ /**
688
+ * Snake-case only the response_format envelope (`jsonSchema` → `json_schema`);
689
+ * the `schema` body is a user-authored JSON schema whose property names must
690
+ * not be rewritten. Inverse of {@link responseFormatFromCp}.
691
+ */
692
+ function responseFormatForCp(
693
+ responseFormat: NonNullable<TfyAgentSpec["responseFormat"]>,
694
+ ): Record<string, unknown> {
695
+ const out: Record<string, unknown> = {};
696
+ for (const [key, value] of Object.entries(responseFormat)) {
697
+ if (key === "jsonSchema" && isRecord(value)) {
698
+ const inner: Record<string, unknown> = {};
699
+ for (const [innerKey, innerValue] of Object.entries(value)) {
700
+ inner[camelToSnakeKey(innerKey)] =
701
+ innerKey === "schema" ? innerValue : toSnakeCaseDeep(innerValue);
702
+ }
703
+ out.json_schema = inner;
704
+ continue;
705
+ }
706
+ out[camelToSnakeKey(key)] = toSnakeCaseDeep(value);
707
+ }
708
+ return out;
709
+ }
710
+
604
711
  /**
605
712
  * Build CP `manifest` for `PUT /api/svc/v1/agents`.
606
- * Normalizes UI catalog mounts, snake_cases gateway fields, hardcodes type /
607
- * metadata_tags / collaborators.
713
+ *
714
+ * Wire-named spec fields are snake-cased and spread so any field the host
715
+ * puts on the spec reaches the wire without this adapter enumerating it.
716
+ * tags / variables / responseFormat are pulled out FIRST because
717
+ * their keys are user data — snake-casing a tag named
718
+ * "TFY_ALPHA_ENABLE_OPENUI" would corrupt it to "_t_f_y__a_l_p_h_a__…" —
719
+ * and re-attached verbatim after the spread.
720
+ *
721
+ * Field order matters:
722
+ * 1. `...snake` — everything wire-named the host provided.
723
+ * 2. description / tags / collaborators — CP requires these, so
724
+ * platform defaults fill in only when the host omitted them.
725
+ * 3. mcp_servers / skills — overwrite the spread values because catalog
726
+ * mounts need remapping to gateway registry shapes (mcpMountForCp /
727
+ * skillMountForCp), which plain snake-casing cannot do.
608
728
  */
609
729
  export function buildSaveAgentManifest(
610
730
  agentName: string,
@@ -613,24 +733,51 @@ export function buildSaveAgentManifest(
613
733
  const spec = normalizeAgentSpecForGateway(agentSpec);
614
734
  const mcpServers = (spec.mcpServers ?? []).map(mcpMountForCp);
615
735
  const skills = (spec.skills ?? []).map(skillMountForCp);
616
- // CP AgentManifest requires description; not on FE AgentSpec yet.
617
- const rawDescription = (agentSpec as { description?: unknown }).description;
618
- const description = typeof rawDescription === "string" ? rawDescription : "";
736
+ const { tags, variables, responseFormat, ...wireFields } = spec;
737
+ const snake = toSnakeCaseDeep(wireFields) as Record<string, unknown>;
619
738
 
620
739
  return {
621
740
  type: "truefoundry-agent",
622
741
  name: agentName,
623
- description,
624
- model: toSnakeCaseDeep(spec.model),
625
- metadata_tags: { ...SAVE_AGENT_METADATA_TAGS },
626
- collaborators: [...SAVE_AGENT_COLLABORATORS],
627
- ...(spec.instructions != null ? { instructions: spec.instructions } : {}),
628
- ...(spec.config != null ? { config: toSnakeCaseDeep(spec.config) } : {}),
742
+ ...snake,
743
+ description: typeof snake.description === "string" ? snake.description : "",
744
+ tags: tags ?? { ...SAVE_AGENT_TAGS },
745
+ collaborators: Array.isArray(snake.collaborators)
746
+ ? snake.collaborators
747
+ : [...SAVE_AGENT_COLLABORATORS],
748
+ ...(variables != null ? { variables } : {}),
749
+ ...(responseFormat != null
750
+ ? { response_format: responseFormatForCp(responseFormat) }
751
+ : {}),
629
752
  ...(mcpServers.length > 0 ? { mcp_servers: mcpServers } : {}),
630
753
  ...(skills.length > 0 ? { skills } : {}),
631
754
  };
632
755
  }
633
756
 
757
+ function nonEmptyString(value: unknown): string | undefined {
758
+ return typeof value === "string" && value !== "" ? value : undefined;
759
+ }
760
+
761
+ /**
762
+ * Normalize CP save responses into
763
+ * `{ agentId, versionId }`. Observed live shapes (both camelCase, wrapped):
764
+ * { data: { id: "<versionId>", agentId: "<agentId>", fqn, version, … } } //agent-playground
765
+ * { data: { id: "<agentId>", name, manifest } } — no separate version //trueforge
766
+ * `id` is a version id only when a distinct `agentId` accompanies it;
767
+ * otherwise `id` is the agent itself and no version id is known.
768
+ */
769
+ export function saveAgentResultFromCp(raw: unknown): TfySaveAgentResult {
770
+ const root = isRecord(raw) ? raw : {};
771
+ const data = isRecord(root.data) ? root.data : {};
772
+ const agentId = nonEmptyString(data.agentId) ?? nonEmptyString(data.id);
773
+ const versionId =
774
+ nonEmptyString(data.agentId) != null ? nonEmptyString(data.id) : undefined;
775
+ return {
776
+ ...(agentId != null ? { agentId } : {}),
777
+ ...(versionId != null ? { versionId } : {}),
778
+ };
779
+ }
780
+
634
781
  /**
635
782
  * Upsert a named agent on the Control Plane.
636
783
  * `PUT /api/svc/v1/agents` with `{ manifest }` — name is the upsert key.
@@ -638,11 +785,12 @@ export function buildSaveAgentManifest(
638
785
  export async function saveAgent(
639
786
  opts: CpCredentials,
640
787
  req: SaveAgentRequest<TfyAgentSpec>,
641
- ): Promise<unknown> {
788
+ ): Promise<TfySaveAgentResult> {
642
789
  const manifest = buildSaveAgentManifest(req.agentName, req.agentSpec);
643
- return cpFetch(opts, "/api/svc/v1/agents", {
790
+ const raw = await cpFetch<unknown>(opts, "/api/svc/v1/agents", {
644
791
  method: "PUT",
645
792
  headers: { "Content-Type": "application/json" },
646
793
  body: JSON.stringify({ manifest }),
647
794
  });
795
+ return saveAgentResultFromCp(raw);
648
796
  }
@@ -15,7 +15,7 @@ import {
15
15
  createTrueFoundryChatServer,
16
16
  type TrueFoundryChatServer,
17
17
  } from "./chatServer.js";
18
- import type { TfyAgentSpec } from "./types.js";
18
+ import type { TfyAgentSpec, TfySaveAgentResult } from "./types.js";
19
19
 
20
20
  export type CreateTrueFoundryAgentUIServerOptions = {
21
21
  apiKey: string;
@@ -37,7 +37,7 @@ export type TrueFoundryAgentUIServer<TSpec extends TfyAgentSpec = TfyAgentSpec>
37
37
  TfySkillSelectorEntry,
38
38
  TfyConnectorSelectorEntry,
39
39
  TfyAgentSelectorEntry,
40
- unknown
40
+ TfySaveAgentResult
41
41
  >;
42
42
 
43
43
  function credentialsKey(opts: CreateTrueFoundryAgentUIServerOptions): string {
@@ -17,9 +17,32 @@ export {
17
17
 
18
18
  export {
19
19
  type RequireApprovalToolSelectorItem,
20
- type RequireApprovalToolsSelectorTag, type TfyAgentSpec, type TfyCreateSessionRequest, type TfyFinishReason, type TfyListSessionsParams, type TfyMcpServerInitInfo, type TfyMcpServerMount, type TfyMcpToolInfo,
21
- type TfyModelMessageUsage, type TfyModelParams, type TfyResponseFormat, type TfyRuntimeConfig, type TfySession, type TfySkillMount, type TfySubject, type TfySystemToolInfo, type TfyThreadState, type TfyToolInfo, type TfyTurn, type TfyTurnCancelledReason, type TfyTurnState, type TfyTurnStateDoneOutput, type ToolsSelectorItem,
22
- type ToolsSelectorTag
20
+ type RequireApprovalToolsSelectorTag,
21
+ type TfyAgentSpec,
22
+ type TfyCollaborator,
23
+ type TfyCreateSessionRequest,
24
+ type TfyFinishReason,
25
+ type TfyListSessionsParams,
26
+ type TfyMcpServerInitInfo,
27
+ type TfyMcpServerMount,
28
+ type TfyMcpToolInfo,
29
+ type TfyModelMessageUsage,
30
+ type TfyModelParams,
31
+ type TfyResponseFormat,
32
+ type TfyRuntimeConfig,
33
+ type TfySaveAgentResult,
34
+ type TfySession,
35
+ type TfySkillMount,
36
+ type TfySubject,
37
+ type TfySystemToolInfo,
38
+ type TfyThreadState,
39
+ type TfyToolInfo,
40
+ type TfyTurn,
41
+ type TfyTurnCancelledReason,
42
+ type TfyTurnState,
43
+ type TfyTurnStateDoneOutput,
44
+ type ToolsSelectorItem,
45
+ type ToolsSelectorTag,
23
46
  } from "./types.js";
24
47
 
25
48
  export {
@@ -16,6 +16,7 @@ import type {
16
16
  AgentSpec,
17
17
  CreateSessionRequest,
18
18
  ListSessionsParams,
19
+ SaveAgentResult,
19
20
  Session,
20
21
  Turn,
21
22
  TurnState,
@@ -63,6 +64,11 @@ export type TfyMcpServerMount = RuntimeMcpServerMount & TruefoundryGatewayApi.Mc
63
64
  // AgentSpec — the concrete TrueFoundry agent definition
64
65
  // ---------------------------------------------------------------------------
65
66
 
67
+ export interface TfyCollaborator {
68
+ subject: string;
69
+ roleId: string;
70
+ }
71
+
66
72
  export interface TfyAgentSpec
67
73
  extends AgentSpec<
68
74
  TruefoundryGatewayApi.Model,
@@ -72,8 +78,12 @@ export interface TfyAgentSpec
72
78
  > {
73
79
  responseFormat?: TruefoundryGatewayApi.ResponseFormat;
74
80
  messages?: TruefoundryGatewayApi.AgentSpecUserMessage[];
81
+ tags?: Record<string, string>;
82
+ collaborators?: TfyCollaborator[];
75
83
  }
76
84
 
85
+ export type TfySaveAgentResult = SaveAgentResult;
86
+
77
87
  // ---------------------------------------------------------------------------
78
88
  // Turn — runtime base narrowed to the gateway's concrete state shapes
79
89
  // ---------------------------------------------------------------------------
@@ -15,6 +15,7 @@ import type {
15
15
  TfyAgentSpec,
16
16
  TfyFinishReason,
17
17
  TfyMcpServerMount,
18
+ TfySaveAgentResult,
18
19
  TfySkillMount,
19
20
  TfySubject,
20
21
  TfyTurn,
@@ -162,3 +163,16 @@ export const withEndTimestamp: NonNullable<
162
163
  startTimestamp: "2026-01-01T00:00:00Z",
163
164
  endTimestamp: "2026-02-01T00:00:00Z",
164
165
  };
166
+
167
+ export const savedAgent: TfyAgentSpec = {
168
+ model: { name: "openai-main/gpt-4.1" },
169
+ description: "Demo",
170
+ tags: { env: "test" },
171
+ collaborators: [{ subject: "team:everyone", roleId: "agent-access" }],
172
+ variables: { city: "Berlin" },
173
+ };
174
+
175
+ export const saveResult: TfySaveAgentResult = {
176
+ agentId: "ag_1",
177
+ versionId: "ver_1",
178
+ };
@@ -35,6 +35,7 @@ export type {
35
35
  UserToolResponseEvent,
36
36
  TurnInputItem,
37
37
  TurnStateRunning,
38
+ TurnDoneMetrics,
38
39
  TurnStateDone,
39
40
  TurnStateCancelled,
40
41
  TurnStateError,
@@ -94,6 +95,13 @@ export type {
94
95
  UpdateSandboxProviderRequest,
95
96
  SandboxCatalogServer,
96
97
  CatalogServer,
98
+ AgentDetail,
99
+ CodeSnippetSampleCode,
100
+ CodeSnippet,
101
+ SessionListMetrics,
102
+ SessionListEntry,
103
+ ListSessionEventsParams,
104
+ AgentSessionsServer,
97
105
  AgentUIServerPort,
98
106
  AgentUIServer,
99
107
  } from "./types.js";
@@ -132,6 +132,7 @@ export interface AgentSpec<
132
132
  config?: TConfig;
133
133
  instructions?: string;
134
134
  variables?: Record<string, string>;
135
+ description?: string;
135
136
  }
136
137
 
137
138
  // ---------------------------------------------------------------------------
@@ -179,10 +180,12 @@ export type PageParams = {
179
180
  };
180
181
 
181
182
  export interface ListSessionsParams extends PageParams {
182
- /** Host-owned agent identity filter. Hosts that key agents by name pass that name here. */
183
+ /** Host-owned agent identity filter. Omit for all sessions (current user). */
183
184
  agentId?: string;
184
- /** Host-specific filter (e.g. TFY startTimestamp). */
185
+ /** Inclusive lower bound on session activity (ISO-8601). */
185
186
  startTimestamp?: string;
187
+ /** Inclusive upper bound on session activity (ISO-8601). */
188
+ endTimestamp?: string;
186
189
  }
187
190
 
188
191
  export type PreviousTurnIdInput = "auto" | "none" | string;
@@ -228,11 +231,26 @@ export type TurnInputItem =
228
231
 
229
232
  export type TurnStateRunning = { status: "running" };
230
233
 
234
+ /**
235
+ * Aggregated token metrics on a finished turn (`turn.done.state.metrics`).
236
+ * Host maps wire snake_case (`total_input_tokens`, …) → camelCase here.
237
+ */
238
+ export interface TurnDoneMetrics {
239
+ totalInputTokens: number;
240
+ totalOutputTokens: number;
241
+ totalTokens: number;
242
+ totalCacheReadTokens: number;
243
+ totalCacheWriteTokens: number;
244
+ totalReasoningTokens: number;
245
+ }
246
+
231
247
  export type TurnStateDone = {
232
248
  status: "done";
233
249
  output?: unknown;
234
250
  requiredActions?: ActionRequiredEvent[];
235
251
  completedAt: string;
252
+ /** Present when the host reports per-turn token totals. */
253
+ metrics?: TurnDoneMetrics;
236
254
  };
237
255
 
238
256
  export type TurnStateCancelled = {
@@ -352,6 +370,8 @@ export interface SaveAgentRequest<TSpec extends AgentSpec = AgentSpec> {
352
370
  export interface SaveAgentResult {
353
371
  /** Immutable id allocated by the host registry. */
354
372
  agentId?: string;
373
+ /** Version id allocated by the host registry for this save. */
374
+ versionId?: string;
355
375
  /** Timestamp returned when the active mutable session was updated. */
356
376
  sessionUpdatedAt?: string;
357
377
  }
@@ -775,20 +795,129 @@ export interface CatalogServer<
775
795
  sandboxCatalog?: TSandboxCatalog;
776
796
  }
777
797
 
798
+ // ---------------------------------------------------------------------------
799
+ // AgentSessions — optional agent-detail shell (Overview + sessions under agent)
800
+ // ---------------------------------------------------------------------------
801
+
802
+ /**
803
+ * Published agent identity + spec for the agent-detail Overview (read-only).
804
+ * Host widens `TSpec` (and may extend this DTO) for mounts / config / extras.
805
+ */
806
+ export interface AgentDetail<TSpec extends AgentSpec = AgentSpec> {
807
+ agentId: string;
808
+ /** Display name (e.g. "release-notes-writer"). */
809
+ name: string;
810
+ agentSpec: TSpec;
811
+ }
812
+
813
+ /** Stream vs non-stream bodies for one language on the Use In Code tab. */
814
+ export interface CodeSnippetSampleCode {
815
+ stream: string;
816
+ nonStream: string;
817
+ }
818
+
819
+ /**
820
+ * One language row for Use In Code.
821
+ * Host maps wire `sample_code` / `non_stream` → camelCase here.
822
+ */
823
+ export interface CodeSnippet<
824
+ TSample extends CodeSnippetSampleCode = CodeSnippetSampleCode,
825
+ > {
826
+ /** Sidebar label (e.g. "TypeScript"). */
827
+ labelName: string;
828
+ /** Highlighter / tab id (e.g. "typescript"). */
829
+ language: string;
830
+ icon?: string;
831
+ sampleCode: TSample;
832
+ }
833
+
834
+ /**
835
+ * Aggregated session metrics for the sessions list sidebar.
836
+ * Host maps wire snake_case (`total_turns`, `total_cost_in_usd`, …) → camelCase.
837
+ */
838
+ export interface SessionListMetrics {
839
+ totalTurns: number;
840
+ totalCostInUsd: number;
841
+ totalDurationMs: number;
842
+ }
843
+
844
+ /**
845
+ * One row in the Agent Sessions list (left pane).
846
+ *
847
+ * Binding: `agentName` → named / immutable agent; `agentSpec` → mutable / draft.
848
+ * Host may send one, both, or neither depending on how the session was created.
849
+ */
850
+ export interface SessionListEntry<TSpec extends AgentSpec = AgentSpec> {
851
+ id: string;
852
+ title?: string | null;
853
+ createdAt: string;
854
+ updatedAt: string;
855
+ lastActivityAt: string;
856
+ metrics: SessionListMetrics;
857
+ /** Present when bound to a published (immutable) agent. */
858
+ agentName?: string | null;
859
+ /** Present when bound to a mutable / draft agent spec. */
860
+ agentSpec?: TSpec;
861
+ }
862
+
863
+ /** Params for `AgentSessionsServer.listSessionEvents` (session event timeline). */
864
+ export interface ListSessionEventsParams extends Pick<PageParams, "limit" | "pageToken"> {
865
+ sessionId: string;
866
+ }
867
+
778
868
  /**
779
- * Composed host port: chat + builder + optional settings catalog.
869
+ * Optional plug-in for agent-detail UI: Overview, Use In Code, sessions list,
870
+ * and per-session event timeline. Omit `sessions` on `AgentUIServerPort` when
871
+ * the host has no agent-detail surface.
872
+ *
873
+ * Read-only — create/update/delete stay on `AgentChatServer` / `AgentBuilderServer`.
874
+ */
875
+ export interface AgentSessionsServer<
876
+ TSpec extends AgentSpec = AgentSpec,
877
+ TDetail extends AgentDetail<TSpec> = AgentDetail<TSpec>,
878
+ TSnippet extends CodeSnippet = CodeSnippet,
879
+ TListEntry extends SessionListEntry<TSpec> = SessionListEntry<TSpec>,
880
+ TList extends ListSessionsParams = ListSessionsParams,
881
+ > {
882
+ /** Fetch published agent details by id for the Overview tab. */
883
+ getAgent(req: { agentId: string }): Promise<TDetail>;
884
+ /** Fetch Use In Code snippets for the agent (one row per language). */
885
+ getCodeSnippets(req: { agentId: string }): Promise<TSnippet[]>;
886
+ /**
887
+ * List sessions for the current user. Pass `agentId` to scope to one agent;
888
+ * omit for all sessions. Use `startTimestamp` / `endTimestamp` for date filters.
889
+ */
890
+ listSessions(req?: TList): Promise<ListResult<TListEntry>>;
891
+ /**
892
+ * Fetch the session event timeline (right pane). Paginate with `pageToken`
893
+ * until exhausted; rebuild turns from `turn.created` / `turn.done` +
894
+ * nested `TurnEvent`s. Per-turn token metrics live on `turn.done.state.metrics`.
895
+ */
896
+ listSessionEvents(
897
+ req: ListSessionEventsParams,
898
+ ): Promise<ListResult<SessionEventItem>>;
899
+ }
900
+
901
+ /**
902
+ * Composed host port: chat + builder + optional settings catalog + optional
903
+ * agent-detail / sessions shell.
780
904
  *
781
905
  * `catalog` is optional — if the host passes it, settings UI can call
782
906
  * `useCatalogServer()` / show modelCatalog, connectorCatalog, and skillCatalog;
783
907
  * if omitted, those surfaces stay hidden.
784
908
  *
909
+ * `sessions` is optional — if the host passes it, agent-detail UI can call
910
+ * `useAgentSessionsServer()` / Overview + sessions under an agent; if omitted,
911
+ * that surface stays hidden.
912
+ *
785
913
  * trueforge-ui re-exports this as `AgentUIServer`.
786
914
  */
787
915
  export type AgentUIServerPort<
788
916
  TChat extends AgentChatServer = AgentChatServer,
789
917
  TBuilder extends AgentBuilderServer = AgentBuilderServer,
790
918
  TCatalog extends CatalogServer = CatalogServer,
791
- > = TChat & TBuilder & { catalog?: TCatalog };
919
+ TSessions extends AgentSessionsServer = AgentSessionsServer,
920
+ > = TChat & TBuilder & { catalog?: TCatalog; sessions?: TSessions };
792
921
 
793
922
  /** Host-facing alias used by trueforge-ui. */
794
923
  export type AgentUIServer = AgentUIServerPort;