@truefoundry/assistant-ui-runtime 0.1.7 → 0.1.9

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +19 -15
  3. package/dist/chunk-CXBZ6WLZ.js +636 -0
  4. package/dist/chunk-CXBZ6WLZ.js.map +1 -0
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.js +14 -5
  7. package/dist/index.js.map +1 -1
  8. package/dist/plugins/truefoundry-agent-server-adapter/index.d.ts +81 -35
  9. package/dist/plugins/truefoundry-agent-server-adapter/index.js +3 -1
  10. package/dist/server/index.d.ts +2 -2
  11. package/dist/{types-DbNsU075.d.ts → types-6yWuWHzK.d.ts} +109 -28
  12. package/package.json +1 -1
  13. package/src/convertTurnMessages.ts +4 -0
  14. package/src/draft/truefoundryDraftThreadListAdapter.ts +4 -1
  15. package/src/harness.temp.ts +85 -0
  16. package/src/index.ts +27 -0
  17. package/src/plugins/truefoundry-agent-server-adapter/README.md +83 -44
  18. package/src/plugins/truefoundry-agent-server-adapter/chatServer.ts +365 -0
  19. package/src/plugins/truefoundry-agent-server-adapter/cp.test.ts +444 -0
  20. package/src/plugins/truefoundry-agent-server-adapter/cp.ts +482 -0
  21. package/src/plugins/truefoundry-agent-server-adapter/createTrueFoundryAgentUIServer.ts +94 -0
  22. package/src/plugins/truefoundry-agent-server-adapter/guards.ts +1 -1
  23. package/src/plugins/truefoundry-agent-server-adapter/index.ts +20 -391
  24. package/src/plugins/truefoundry-agent-server-adapter/normalizeAgentSpec.test.ts +85 -0
  25. package/src/plugins/truefoundry-agent-server-adapter/normalizeAgentSpec.ts +84 -0
  26. package/src/plugins/truefoundry-agent-server-adapter/types.ts +1 -1
  27. package/src/server/index.ts +20 -0
  28. package/src/server/types.ts +125 -28
  29. package/src/streamTurn.test.ts +27 -27
  30. package/src/streamTurn.ts +2 -2
  31. package/src/truefoundryOwnedSessionsThreadListAdapter.ts +4 -1
  32. package/src/truefoundryThreadListAdapter.test.ts +22 -0
  33. package/src/truefoundryThreadListAdapter.ts +4 -1
  34. package/src/useTrueFoundryAgentMessages.test.tsx +1 -1
  35. package/dist/chunk-SQDOTGP2.js +0 -292
  36. package/dist/chunk-SQDOTGP2.js.map +0 -1
@@ -21,6 +21,7 @@ import type {
21
21
  export interface ModelSelectorEntry {
22
22
  name: string;
23
23
  provider: string;
24
+ reasoningEfforts?: string[];
24
25
  }
25
26
 
26
27
  /** Skill selector row. Host extends for fqn, preload, etc. */
@@ -249,7 +250,7 @@ export interface AgentChatServer<
249
250
  getSession(req: { sessionId: string }): Promise<TSession>;
250
251
  updateSession(req: TUpdate): Promise<TSession>;
251
252
 
252
- prepareAndExecuteTurn(req: {
253
+ createTurn(req: {
253
254
  sessionId: string;
254
255
  input?: TurnInputItem[];
255
256
  previousTurnId?: PreviousTurnIdInput;
@@ -405,33 +406,36 @@ export interface ModelCatalogServer<
405
406
  export interface ToolBase {
406
407
  id: string;
407
408
  name: string;
409
+ description: string;
408
410
  }
409
411
 
410
- /**
411
- * Auth type id. Reserved literals: `"None"`, `"OAuth"`, `"API Key"`.
412
- * Stays `string` so hosts can widen (same pattern as `ProviderType`).
413
- */
414
- export type ConnectorAuthType = string;
412
+ /** Strict auth type id. Hosts widen branches via intersection + re-union. */
413
+ export type ConnectorAuthType = "dcr" | "header" | "none";
415
414
 
416
- /**
417
- * Write-time connector auth. Host extends / narrows via `TType`.
418
- * For `"API Key"`, pass `apiKey` (and optional `headerName`).
419
- */
420
- export interface ConnectorAuth<TType extends ConnectorAuthType = ConnectorAuthType> {
421
- type: TType;
415
+ // Write (create/update) — export branches so hosts can intersect extras
416
+ export type ConnectorAuthOAuth = { type: "dcr"; authUrl?: string };
417
+ export type ConnectorAuthApiKey = {
418
+ type: "header";
422
419
  apiKey?: string;
423
420
  headerName?: string;
424
- }
425
-
426
- /**
427
- * Catalog / list auth — no secrets. Host extends / narrows via `TType`.
428
- */
429
- export interface ConnectorAuthPublic<
430
- TType extends ConnectorAuthType = ConnectorAuthType,
431
- > {
432
- type: TType;
421
+ };
422
+ export type ConnectorAuthNone = { type: "none" };
423
+ export type ConnectorAuth =
424
+ | ConnectorAuthOAuth
425
+ | ConnectorAuthApiKey
426
+ | ConnectorAuthNone;
427
+
428
+ // Public (list/detail) — no secrets; dcr requires authUrl
429
+ export type ConnectorAuthPublicOAuth = { type: "dcr"; authUrl: string };
430
+ export type ConnectorAuthPublicApiKey = {
431
+ type: "header";
433
432
  headerName?: string;
434
- }
433
+ };
434
+ export type ConnectorAuthPublicNone = { type: "none" };
435
+ export type ConnectorAuthPublic =
436
+ | ConnectorAuthPublicOAuth
437
+ | ConnectorAuthPublicApiKey
438
+ | ConnectorAuthPublicNone;
435
439
 
436
440
  /**
437
441
  * MCP / connector create-edit config. Host extends for extra fields, etc.
@@ -457,6 +461,8 @@ export interface ConnectorBase<
457
461
  description: string;
458
462
  url: string;
459
463
  auth: TAuth;
464
+ /** When true, UI should not show Disconnect. */
465
+ requiresAuth: boolean;
460
466
  authenticated: boolean;
461
467
  tools: TTool[];
462
468
  }
@@ -500,7 +506,10 @@ export interface ConnectorCatalogServer<
500
506
  createConnector(req: TCreate): Promise<TConnector>;
501
507
  /** Full replace update keyed by connector `id`. */
502
508
  updateConnector(req: TUpdate): Promise<TConnector>;
503
- /** Start connector auth (e.g. OAuth). Host may widen return with `authUrl`. */
509
+ /**
510
+ * Start connector auth (e.g. OAuth).
511
+ * For oauth, the returned connector's `auth.authUrl` is the authorize URL.
512
+ */
504
513
  authenticateConnector(req: { id: string }): Promise<TConnector>;
505
514
  /** Clear connector auth. */
506
515
  disconnectConnector(req: { id: string }): Promise<TConnector>;
@@ -518,34 +527,122 @@ export interface SkillBase {
518
527
  description: string;
519
528
  }
520
529
 
521
- /** Create-skill request. Host extends for branch, auth, etc. */
522
- export interface CreateSkillRequest {
523
- repo: string;
524
- directory: string;
530
+ export interface RegistrySkill extends SkillBase {
531
+ /** `SkillCatalogEntry.id` this skill was created from. */
532
+ catalogId: string;
525
533
  }
526
534
 
535
+ export interface GithubSkill extends SkillBase {}
536
+
537
+ export type DefinedSkill = RegistrySkill | GithubSkill;
538
+
539
+ /** Git source fields shared by catalog entries and create requests. */
540
+ export interface SkillConfigBase {
541
+ name: string;
542
+ description: string;
543
+ repoURL: string;
544
+ path: string;
545
+ ref: string;
546
+ }
547
+
548
+ export interface SkillCatalogEntry extends SkillConfigBase {
549
+ id: string;
550
+ }
551
+
552
+ /** Create-skill base. Hosts may intersect extra fields and re-union. */
553
+ export interface CreateSkillRequestBase extends SkillConfigBase {}
554
+
555
+ export interface SelectRegistrySkillRequest extends CreateSkillRequestBase {
556
+ /** `SkillCatalogEntry.id`, persisted so the created skill links back to it. */
557
+ catalogId: string;
558
+ }
559
+
560
+ export interface ImportGithubSkillRequest extends CreateSkillRequestBase {}
561
+
562
+ export type CreateSkillRequest =
563
+ | SelectRegistrySkillRequest
564
+ | ImportGithubSkillRequest;
565
+
527
566
  export interface SkillCatalogServer<
528
567
  TSkill extends SkillBase = SkillBase,
568
+ TCatalogEntry extends SkillCatalogEntry = SkillCatalogEntry,
529
569
  TCreate extends CreateSkillRequest = CreateSkillRequest,
530
570
  > {
571
+ getSkillCatalog(): Promise<TCatalogEntry[]>;
531
572
  listSkills(req?: { query?: string }): Promise<TSkill[]>;
532
573
  createSkill(req: TCreate): Promise<TSkill>;
533
574
  deleteSkill?(req: { id: string }): Promise<void>;
534
575
  }
535
576
 
577
+ // ---------------------------------------------------------------------------
578
+ // Sandboxes catalog — public rows omit credentials; writes accept them
579
+ // ---------------------------------------------------------------------------
580
+
581
+ /** Mutable sandbox settings shared by catalog rows, create, and update. */
582
+ export interface SandboxConfig {
583
+ snapshotName: string;
584
+ execTimeoutMs: number;
585
+ autoStopIntervalInMinutes: number;
586
+ autoArchiveIntervalInMinutes: number;
587
+ autoDeleteIntervalInMinutes: number;
588
+ }
589
+
590
+ export interface SandboxCatalogEntry extends SandboxConfig {
591
+ id: string;
592
+ name: string;
593
+ type: string;
594
+ }
595
+
596
+ export interface SandboxBase {
597
+ id: string;
598
+ name: string;
599
+ catalogId: string;
600
+ isConnected: boolean;
601
+ }
602
+
603
+ export interface CreateSandboxRequest extends SandboxConfig {
604
+ /** `SandboxCatalogEntry.id` used to create this sandbox. */
605
+ catalogId: string;
606
+ name: string;
607
+ type: string;
608
+ apiKey: string;
609
+ }
610
+
611
+ export interface UpdateSandboxRequest extends SandboxConfig {
612
+ id: string;
613
+ apiKey: string;
614
+ }
615
+
616
+ export interface SandboxCatalogServer<
617
+ TSandbox extends SandboxBase = SandboxBase,
618
+ TCatalogEntry extends SandboxCatalogEntry = SandboxCatalogEntry,
619
+ TCreate extends CreateSandboxRequest = CreateSandboxRequest,
620
+ TUpdate extends UpdateSandboxRequest = UpdateSandboxRequest,
621
+ > {
622
+ getSandboxCatalog(): Promise<TCatalogEntry[]>;
623
+ listSandboxes(req?: { query?: string }): Promise<TSandbox[]>;
624
+ createSandbox(req: TCreate): Promise<TSandbox>;
625
+ updateSandbox(req: TUpdate): Promise<TSandbox>;
626
+ deleteSandbox(req: { id: string }): Promise<void>;
627
+ }
628
+
536
629
  /**
537
- * Settings management aggregate — modelCatalog + connectorCatalog + optional skillCatalog.
630
+ * Settings management aggregate — modelCatalog + connectorCatalog + optional
631
+ * skill and sandbox catalogs.
538
632
  * Hosts may pass the whole object to an app shell, or a focused sub-port to a page.
539
633
  */
540
634
  export interface CatalogServer<
541
635
  TModelCatalog extends ModelCatalogServer = ModelCatalogServer,
542
636
  TConnectorCatalog extends ConnectorCatalogServer = ConnectorCatalogServer,
543
637
  TSkillCatalog extends SkillCatalogServer = SkillCatalogServer,
638
+ TSandboxCatalog extends SandboxCatalogServer = SandboxCatalogServer,
544
639
  > {
545
640
  modelCatalog: TModelCatalog;
546
641
  connectorCatalog: TConnectorCatalog;
547
642
  /** Optional — omit when the host has no skills settings surface. */
548
643
  skillCatalog?: TSkillCatalog;
644
+ /** Optional — omit when the host has no sandboxes settings surface. */
645
+ sandboxCatalog?: TSandboxCatalog;
549
646
  }
550
647
 
551
648
  /**
@@ -33,7 +33,7 @@ describe("streamTurn", () => {
33
33
  describe("streamTurnContent", () => {
34
34
  it("prepares a user turn and yields folded stream updates", async () => {
35
35
  const foldState = new PeerThreadFoldState();
36
- const prepareAndExecuteTurn = vi.fn(async function* () {
36
+ const createTurn = vi.fn(async function* () {
37
37
  yield streamData(1, {
38
38
  type: "model.message",
39
39
  createdAt,
@@ -43,7 +43,7 @@ describe("streamTurn", () => {
43
43
  });
44
44
  });
45
45
  const server = mockServer({
46
- prepareAndExecuteTurn,
46
+ createTurn,
47
47
  cancelSession: vi.fn().mockResolvedValue(undefined),
48
48
  });
49
49
 
@@ -57,7 +57,7 @@ describe("streamTurn", () => {
57
57
  ),
58
58
  );
59
59
 
60
- expect(prepareAndExecuteTurn).toHaveBeenCalledWith({
60
+ expect(createTurn).toHaveBeenCalledWith({
61
61
  sessionId: SESSION_ID,
62
62
  input: [{ type: "user.message", content: "hello" }],
63
63
  previousTurnId: "auto",
@@ -68,7 +68,7 @@ describe("streamTurn", () => {
68
68
  ]);
69
69
  });
70
70
 
71
- it("passes required-action inputs through prepareAndExecuteTurn", async () => {
71
+ it("passes required-action inputs through createTurn", async () => {
72
72
  const inputs = [
73
73
  {
74
74
  type: "user.tool_approval" as const,
@@ -83,9 +83,9 @@ describe("streamTurn", () => {
83
83
  content: "A",
84
84
  },
85
85
  ];
86
- const prepareAndExecuteTurn = vi.fn(async function* () {});
86
+ const createTurn = vi.fn(async function* () {});
87
87
  const server = mockServer({
88
- prepareAndExecuteTurn,
88
+ createTurn,
89
89
  cancelSession: vi.fn().mockResolvedValue(undefined),
90
90
  });
91
91
 
@@ -99,7 +99,7 @@ describe("streamTurn", () => {
99
99
  ),
100
100
  );
101
101
 
102
- expect(prepareAndExecuteTurn).toHaveBeenCalledWith({
102
+ expect(createTurn).toHaveBeenCalledWith({
103
103
  sessionId: SESSION_ID,
104
104
  input: inputs,
105
105
  previousTurnId: "auto",
@@ -108,9 +108,9 @@ describe("streamTurn", () => {
108
108
  });
109
109
 
110
110
  it("uses empty input when resuming after MCP auth", async () => {
111
- const prepareAndExecuteTurn = vi.fn(async function* () {});
111
+ const createTurn = vi.fn(async function* () {});
112
112
  const server = mockServer({
113
- prepareAndExecuteTurn,
113
+ createTurn,
114
114
  cancelSession: vi.fn().mockResolvedValue(undefined),
115
115
  });
116
116
 
@@ -124,7 +124,7 @@ describe("streamTurn", () => {
124
124
  ),
125
125
  );
126
126
 
127
- expect(prepareAndExecuteTurn).toHaveBeenCalledWith({
127
+ expect(createTurn).toHaveBeenCalledWith({
128
128
  sessionId: SESSION_ID,
129
129
  input: [],
130
130
  previousTurnId: "auto",
@@ -133,9 +133,9 @@ describe("streamTurn", () => {
133
133
  });
134
134
 
135
135
  it("forwards an explicit previousTurnId when branching", async () => {
136
- const prepareAndExecuteTurn = vi.fn(async function* () {});
136
+ const createTurn = vi.fn(async function* () {});
137
137
  const server = mockServer({
138
- prepareAndExecuteTurn,
138
+ createTurn,
139
139
  cancelSession: vi.fn().mockResolvedValue(undefined),
140
140
  });
141
141
 
@@ -149,7 +149,7 @@ describe("streamTurn", () => {
149
149
  ),
150
150
  );
151
151
 
152
- expect(prepareAndExecuteTurn).toHaveBeenCalledWith({
152
+ expect(createTurn).toHaveBeenCalledWith({
153
153
  sessionId: SESSION_ID,
154
154
  input: [{ type: "user.message", content: "edited" }],
155
155
  previousTurnId: "turn-a",
@@ -158,9 +158,9 @@ describe("streamTurn", () => {
158
158
  });
159
159
 
160
160
  it("forwards previousTurnId \"none\" when branching from root", async () => {
161
- const prepareAndExecuteTurn = vi.fn(async function* () {});
161
+ const createTurn = vi.fn(async function* () {});
162
162
  const server = mockServer({
163
- prepareAndExecuteTurn,
163
+ createTurn,
164
164
  cancelSession: vi.fn().mockResolvedValue(undefined),
165
165
  });
166
166
 
@@ -174,7 +174,7 @@ describe("streamTurn", () => {
174
174
  ),
175
175
  );
176
176
 
177
- expect(prepareAndExecuteTurn).toHaveBeenCalledWith({
177
+ expect(createTurn).toHaveBeenCalledWith({
178
178
  sessionId: SESSION_ID,
179
179
  input: [{ type: "user.message", content: "first" }],
180
180
  previousTurnId: "none",
@@ -183,9 +183,9 @@ describe("streamTurn", () => {
183
183
  });
184
184
 
185
185
  it("returns early and cancels the session when already aborted", async () => {
186
- const prepareAndExecuteTurn = vi.fn(async function* () {});
186
+ const createTurn = vi.fn(async function* () {});
187
187
  const cancelSession = vi.fn().mockResolvedValue(undefined);
188
- const server = mockServer({ prepareAndExecuteTurn, cancelSession });
188
+ const server = mockServer({ createTurn, cancelSession });
189
189
  const abortController = new AbortController();
190
190
  abortController.abort();
191
191
 
@@ -200,14 +200,14 @@ describe("streamTurn", () => {
200
200
  );
201
201
 
202
202
  expect(cancelSession).toHaveBeenCalledWith({ sessionId: SESSION_ID });
203
- expect(prepareAndExecuteTurn).not.toHaveBeenCalled();
203
+ expect(createTurn).not.toHaveBeenCalled();
204
204
  expect(updates).toEqual([]);
205
205
  });
206
206
 
207
- it("forwards headers to prepareAndExecuteTurn", async () => {
208
- const prepareAndExecuteTurn = vi.fn(async function* () {});
207
+ it("forwards headers to createTurn", async () => {
208
+ const createTurn = vi.fn(async function* () {});
209
209
  const server = mockServer({
210
- prepareAndExecuteTurn,
210
+ createTurn,
211
211
  cancelSession: vi.fn().mockResolvedValue(undefined),
212
212
  });
213
213
  const abortSignal = new AbortController().signal;
@@ -227,7 +227,7 @@ describe("streamTurn", () => {
227
227
  ),
228
228
  );
229
229
 
230
- expect(prepareAndExecuteTurn).toHaveBeenCalledWith({
230
+ expect(createTurn).toHaveBeenCalledWith({
231
231
  sessionId: SESSION_ID,
232
232
  input: [{ type: "user.message", content: "hello" }],
233
233
  previousTurnId: "auto",
@@ -240,7 +240,7 @@ describe("streamTurn", () => {
240
240
 
241
241
  it("notifies gateway turn id when turn.done errors with no content yields", async () => {
242
242
  const gatewayTurnId = "01ky6mqzmczwt6ssyd5r02gjjc";
243
- const prepareAndExecuteTurn = vi.fn(async function* () {
243
+ const createTurn = vi.fn(async function* () {
244
244
  yield streamData(1, {
245
245
  type: "turn.created",
246
246
  createdAt,
@@ -261,7 +261,7 @@ describe("streamTurn", () => {
261
261
  });
262
262
  });
263
263
  const server = mockServer({
264
- prepareAndExecuteTurn,
264
+ createTurn,
265
265
  cancelSession: vi.fn().mockResolvedValue(undefined),
266
266
  });
267
267
  const onTurnIdAvailable = vi.fn();
@@ -285,7 +285,7 @@ describe("streamTurn", () => {
285
285
  });
286
286
 
287
287
  it("does not notify when an error stream never emits turn.created", async () => {
288
- const prepareAndExecuteTurn = vi.fn(async function* () {
288
+ const createTurn = vi.fn(async function* () {
289
289
  yield streamData(1, {
290
290
  type: "turn.done",
291
291
  createdAt,
@@ -298,7 +298,7 @@ describe("streamTurn", () => {
298
298
  });
299
299
  });
300
300
  const server = mockServer({
301
- prepareAndExecuteTurn,
301
+ createTurn,
302
302
  cancelSession: vi.fn().mockResolvedValue(undefined),
303
303
  });
304
304
  const onTurnIdAvailable = vi.fn();
package/src/streamTurn.ts CHANGED
@@ -18,7 +18,7 @@ export type StreamTurnOptions = {
18
18
  resumeMcpAuth?: boolean;
19
19
  inputs?: RequiredActionInput[];
20
20
  /**
21
- * Branch anchor for prepareAndExecuteTurn. Omit for `"auto"`. Pass `"none"` for a fresh
21
+ * Branch anchor for createTurn. Omit for `"auto"`. Pass `"none"` for a fresh
22
22
  * root turn.
23
23
  */
24
24
  previousTurnId?: PreviousTurnIdInput;
@@ -79,7 +79,7 @@ export async function* streamTurnContent(
79
79
  }
80
80
  };
81
81
 
82
- const stream: AsyncIterable<TurnStreamData> = server.prepareAndExecuteTurn({
82
+ const stream: AsyncIterable<TurnStreamData> = server.createTurn({
83
83
  sessionId,
84
84
  input: buildTurnInput(options),
85
85
  previousTurnId: options.previousTurnId ?? "auto",
@@ -63,7 +63,10 @@ export function createTrueFoundryOwnedSessionsThreadListAdapter(options: {
63
63
  async rename() {},
64
64
  async archive() {},
65
65
  async unarchive() {},
66
- async delete() {},
66
+ async delete(remoteId) {
67
+ if (typeof server.deleteSession !== "function") return;
68
+ await server.deleteSession({ sessionId: remoteId });
69
+ },
67
70
 
68
71
  async generateTitle() {
69
72
  return new ReadableStream();
@@ -98,4 +98,26 @@ describe("createTrueFoundryThreadListAdapter", () => {
98
98
 
99
99
  expect(result.nextCursor).toBeUndefined();
100
100
  });
101
+
102
+ it("delete calls server.deleteSession when implemented", async () => {
103
+ const deleteSession = vi.fn().mockResolvedValue(undefined);
104
+ const server = mockServer({ deleteSession });
105
+ const adapter = createTrueFoundryThreadListAdapter({
106
+ server,
107
+ agentName: "my-agent",
108
+ });
109
+
110
+ await adapter.delete("s1");
111
+
112
+ expect(deleteSession).toHaveBeenCalledWith({ sessionId: "s1" });
113
+ });
114
+
115
+ it("delete is a no-op when server.deleteSession is missing", async () => {
116
+ const adapter = createTrueFoundryThreadListAdapter({
117
+ server: mockServer({}),
118
+ agentName: "my-agent",
119
+ });
120
+
121
+ await expect(adapter.delete("s1")).resolves.toBeUndefined();
122
+ });
101
123
  });
@@ -50,7 +50,10 @@ export function createTrueFoundryThreadListAdapter(options: {
50
50
  async rename() {},
51
51
  async archive() {},
52
52
  async unarchive() {},
53
- async delete() {},
53
+ async delete(remoteId) {
54
+ if (typeof server.deleteSession !== "function") return;
55
+ await server.deleteSession({ sessionId: remoteId });
56
+ },
54
57
 
55
58
  async generateTitle() {
56
59
  return new ReadableStream();
@@ -994,7 +994,7 @@ describe("useTrueFoundryAgentMessages", () => {
994
994
  });
995
995
 
996
996
  describe("batched resume invariant", () => {
997
- it("issues exactly one prepareAndExecuteTurn input batch across root and sub-agent threads", async () => {
997
+ it("issues exactly one createTurn input batch across root and sub-agent threads", async () => {
998
998
  vi.mocked(loadSessionSnapshot).mockResolvedValue(
999
999
  snapshotWithAssistantMessage(assistantMessageWithMultiThreadPendingActions()),
1000
1000
  );