@truefoundry/assistant-ui-runtime 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/{chunk-CXBZ6WLZ.js → chunk-6ZOGBD7E.js} +99 -17
- package/dist/chunk-6ZOGBD7E.js.map +1 -0
- package/dist/index.d.ts +26 -5
- package/dist/index.js +92 -71
- package/dist/index.js.map +1 -1
- package/dist/plugins/truefoundry-agent-server-adapter/index.d.ts +1 -1
- package/dist/plugins/truefoundry-agent-server-adapter/index.js +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/{types-6yWuWHzK.d.ts → types-Bb8Kaf1h.d.ts} +16 -3
- package/package.json +3 -3
- package/src/convertTurnMessages.test.ts +23 -1
- package/src/convertTurnMessages.ts +9 -3
- package/src/draft/truefoundryDraftThreadListAdapter.test.ts +57 -4
- package/src/draft/truefoundryDraftThreadListAdapter.ts +18 -22
- package/src/hooks.ts +23 -3
- package/src/index.ts +1 -0
- package/src/messageCustomMetadata.ts +2 -0
- package/src/plugins/truefoundry-agent-server-adapter/chatServer.ts +7 -6
- package/src/plugins/truefoundry-agent-server-adapter/cp.test.ts +169 -6
- package/src/plugins/truefoundry-agent-server-adapter/cp.ts +126 -1
- package/src/plugins/truefoundry-agent-server-adapter/normalizeAgentSpec.test.ts +34 -0
- package/src/plugins/truefoundry-agent-server-adapter/normalizeAgentSpec.ts +12 -5
- package/src/server/types.ts +18 -6
- package/src/sessionThreadMetadata.ts +39 -0
- package/src/truefoundryExtras.ts +1 -1
- package/src/truefoundryOwnedSessionsThreadListAdapter.test.ts +16 -0
- package/src/truefoundryOwnedSessionsThreadListAdapter.ts +13 -25
- package/src/truefoundryThreadListAdapter.test.ts +17 -9
- package/src/truefoundryThreadListAdapter.ts +9 -14
- package/src/types.ts +5 -0
- package/src/useTrueFoundryAgentRuntime.ts +17 -4
- package/dist/chunk-CXBZ6WLZ.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentSessionClient } from 'truefoundry-gateway-sdk/agents';
|
|
2
2
|
import { PrivateAgentSessionClient } from 'truefoundry-gateway-sdk/agents/private';
|
|
3
|
-
import { A as AgentSpec, x as CreateSessionRequest, B as ListSessionsParams, S as Session, d as Turn, a7 as TurnState, a as AgentChatServer, ae as UpdateSessionRequest, ag as AgentSelectorEntry, ah as ConnectorSelectorEntry, ai as ModelSelectorEntry, aj as SkillSelectorEntry, f as AgentBuilderServer } from '../../types-
|
|
3
|
+
import { A as AgentSpec, x as CreateSessionRequest, B as ListSessionsParams, S as Session, d as Turn, a7 as TurnState, a as AgentChatServer, ae as UpdateSessionRequest, ag as AgentSelectorEntry, ah as ConnectorSelectorEntry, ai as ModelSelectorEntry, aj as SkillSelectorEntry, f as AgentBuilderServer } from '../../types-Bb8Kaf1h.js';
|
|
4
4
|
import { TruefoundryGatewayApi } from 'truefoundry-gateway-sdk';
|
|
5
5
|
|
|
6
6
|
/**
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { aa as TurnStreamingEvent, ak as DeltaEvents, T as TurnEvent } from '../types-
|
|
2
|
-
export { al as ActionRequiredEvent, f as AgentBuilderServer, a as AgentChatServer, am as AgentInfo, an as AgentParent, ag as AgentSelectorEntry, A as AgentSpec, g as AgentUIServerPort, ao as ApprovalDecision, C as CatalogServer, ap as ChunkDeltaToolCall, h as ConnectorAuth, i as ConnectorAuthApiKey, j as ConnectorAuthNone, k as ConnectorAuthOAuth, l as ConnectorAuthPublic, m as ConnectorAuthPublicApiKey, n as ConnectorAuthPublicNone, o as ConnectorAuthPublicOAuth, p as ConnectorAuthType, q as ConnectorBase, r as ConnectorCatalogEntry, s as ConnectorCatalogServer, t as ConnectorConfigBase, ah as ConnectorSelectorEntry, u as CreateConnectorRequest, v as CreateModelProviderRequest, w as CreateSandboxRequest, x as CreateSessionRequest, y as CreateSkillRequest, z as CreateSkillRequestBase, D as DefinedSkill, G as GithubSkill, I as ImportGithubSkillRequest, L as ListResult, aq as ListSessionsOrder, B as ListSessionsParams, M as McpAuthRequiredEvent, ar as McpInitializeEvent, as as McpServerAuthInfo, at as McpServerMount, au as Model, E as ModelCatalogServer, F as ModelEntry, av as ModelMessageContentPart, aw as ModelMessageDeltaEvent, H as ModelMessageEvent, ax as ModelParams, J as ModelProviderBase, K as ModelProviderCatalogEntry, N as ModelProviderConfigBase, ai as ModelSelectorEntry, ay as PageParams, P as PreviousTurnIdInput, O as ProviderType, R as RegistrySkill, Q as SandboxBase, V as SandboxCatalogEntry, W as SandboxCatalogServer, X as SandboxConfig, Y as SandboxCreatedEvent, az as SearchAgentSelectorParams, Z as SelectRegistrySkillRequest, S as Session, _ as SessionEventItem, $ as SkillBase, a0 as SkillCatalogEntry, a1 as SkillCatalogServer, a2 as SkillConfigBase, aA as SkillMount, aj as SkillSelectorEntry, b as ThreadCreatedEvent, aB as ThreadDoneEvent, a3 as ToolApprovalRequiredEvent, a4 as ToolBase, a5 as ToolCall, aC as ToolCallFunction, aD as ToolCallRef, aE as ToolInfo, aF as ToolResponseEvent, a6 as ToolResponseRequiredEvent, d as Turn, aG as TurnCreatedEvent, aH as TurnDoneEvent, e as TurnInputItem, a7 as TurnState, aI as TurnStateCancelled, a8 as TurnStateDone, aJ as TurnStateError, aK as TurnStateRunning, a9 as TurnStreamData, ab as UpdateConnectorRequest, ac as UpdateModelProviderRequest, ad as UpdateSandboxRequest, ae as UpdateSessionRequest, af as UserMessage, aL as UserMessageContent, c as UserToolApprovalEvent, U as UserToolResponseEvent } from '../types-
|
|
1
|
+
import { aa as TurnStreamingEvent, ak as DeltaEvents, T as TurnEvent } from '../types-Bb8Kaf1h.js';
|
|
2
|
+
export { al as ActionRequiredEvent, f as AgentBuilderServer, a as AgentChatServer, am as AgentInfo, an as AgentParent, ag as AgentSelectorEntry, A as AgentSpec, g as AgentUIServerPort, ao as ApprovalDecision, C as CatalogServer, ap as ChunkDeltaToolCall, h as ConnectorAuth, i as ConnectorAuthApiKey, j as ConnectorAuthNone, k as ConnectorAuthOAuth, l as ConnectorAuthPublic, m as ConnectorAuthPublicApiKey, n as ConnectorAuthPublicNone, o as ConnectorAuthPublicOAuth, p as ConnectorAuthType, q as ConnectorBase, r as ConnectorCatalogEntry, s as ConnectorCatalogServer, t as ConnectorConfigBase, ah as ConnectorSelectorEntry, u as CreateConnectorRequest, v as CreateModelProviderRequest, w as CreateSandboxRequest, x as CreateSessionRequest, y as CreateSkillRequest, z as CreateSkillRequestBase, D as DefinedSkill, G as GithubSkill, I as ImportGithubSkillRequest, L as ListResult, aq as ListSessionsOrder, B as ListSessionsParams, M as McpAuthRequiredEvent, ar as McpInitializeEvent, as as McpServerAuthInfo, at as McpServerMount, au as Model, E as ModelCatalogServer, F as ModelEntry, av as ModelMessageContentPart, aw as ModelMessageDeltaEvent, H as ModelMessageEvent, ax as ModelParams, J as ModelProviderBase, K as ModelProviderCatalogEntry, N as ModelProviderConfigBase, ai as ModelSelectorEntry, ay as PageParams, P as PreviousTurnIdInput, O as ProviderType, R as RegistrySkill, Q as SandboxBase, V as SandboxCatalogEntry, W as SandboxCatalogServer, X as SandboxConfig, Y as SandboxCreatedEvent, az as SearchAgentSelectorParams, Z as SelectRegistrySkillRequest, S as Session, _ as SessionEventItem, $ as SkillBase, a0 as SkillCatalogEntry, a1 as SkillCatalogServer, a2 as SkillConfigBase, aA as SkillMount, aj as SkillSelectorEntry, b as ThreadCreatedEvent, aB as ThreadDoneEvent, a3 as ToolApprovalRequiredEvent, a4 as ToolBase, a5 as ToolCall, aC as ToolCallFunction, aD as ToolCallRef, aE as ToolInfo, aF as ToolResponseEvent, a6 as ToolResponseRequiredEvent, d as Turn, aG as TurnCreatedEvent, aH as TurnDoneEvent, e as TurnInputItem, a7 as TurnState, aI as TurnStateCancelled, a8 as TurnStateDone, aJ as TurnStateError, aK as TurnStateRunning, a9 as TurnStreamData, ab as UpdateConnectorRequest, ac as UpdateModelProviderRequest, ad as UpdateSandboxRequest, ae as UpdateSessionRequest, af as UserMessage, aL as UserMessageContent, c as UserToolApprovalEvent, U as UserToolResponseEvent } from '../types-Bb8Kaf1h.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Local implementations of streaming delta helpers.
|
|
@@ -245,9 +245,13 @@ interface ConnectorSelectorEntry {
|
|
|
245
245
|
name: string;
|
|
246
246
|
description?: string;
|
|
247
247
|
}
|
|
248
|
-
/** Agent selector row
|
|
248
|
+
/** Agent selector row. Host extends for metadata; `agentSpec` enables Edit. */
|
|
249
249
|
interface AgentSelectorEntry {
|
|
250
250
|
name: string;
|
|
251
|
+
/** Stable id when distinct from display `name`. Falls back to `name` when omitted. */
|
|
252
|
+
agentId?: string;
|
|
253
|
+
/** Published agent spec — required for Edit; optional for Try-only hosts. */
|
|
254
|
+
agentSpec?: AgentSpec;
|
|
251
255
|
}
|
|
252
256
|
type SearchAgentSelectorParams = {
|
|
253
257
|
query?: string;
|
|
@@ -321,7 +325,8 @@ type PageParams = {
|
|
|
321
325
|
pageToken?: string;
|
|
322
326
|
};
|
|
323
327
|
interface ListSessionsParams extends PageParams {
|
|
324
|
-
|
|
328
|
+
/** Host-owned agent identity filter. Hosts that key agents by name pass that name here. */
|
|
329
|
+
agentId?: string;
|
|
325
330
|
/** Host-specific filter (e.g. TFY startTimestamp). */
|
|
326
331
|
startTimestamp?: string;
|
|
327
332
|
}
|
|
@@ -442,7 +447,15 @@ interface AgentChatServer<TSpec extends AgentSpec = AgentSpec, TSession extends
|
|
|
442
447
|
afterSequenceNumber?: number;
|
|
443
448
|
abortSignal?: AbortSignal;
|
|
444
449
|
}): AsyncIterable<TurnStreamData>;
|
|
445
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Reads a file the agent wrote inside its sandbox. Hosts whose download route is scoped to a
|
|
452
|
+
* turn resolve the sandbox from `turnId` and ignore `sandboxId`; hosts addressing sandboxes
|
|
453
|
+
* directly use `sandboxId`.
|
|
454
|
+
*/
|
|
455
|
+
downloadSandboxFile?(req: {
|
|
456
|
+
sessionId: string;
|
|
457
|
+
turnId: string;
|
|
458
|
+
sandboxId: string;
|
|
446
459
|
path: string;
|
|
447
460
|
}): Promise<Blob>;
|
|
448
461
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truefoundry/assistant-ui-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "TrueFoundry Gateway agent runtime adapter for assistant-ui",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"typecheck": "tsc --noEmit"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@assistant-ui/core": "^0.2.
|
|
58
|
-
"@assistant-ui/store": "^0.2.
|
|
57
|
+
"@assistant-ui/core": "^0.2.22",
|
|
58
|
+
"@assistant-ui/store": "^0.2.21"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@types/react": "*",
|
|
@@ -461,6 +461,26 @@ describe("convertTurnMessages", () => {
|
|
|
461
461
|
expect(updated.id).toBe(existing.id);
|
|
462
462
|
expect(updated.content).toEqual([{ type: "text", text: "second" }]);
|
|
463
463
|
});
|
|
464
|
+
|
|
465
|
+
it("stamps the streaming turn id onto the message metadata", () => {
|
|
466
|
+
const message = turnStreamUpdateToAssistantMessage("turn-1", {
|
|
467
|
+
content: [{ type: "text", text: "hi" }],
|
|
468
|
+
});
|
|
469
|
+
expect(message.metadata.custom.turnId).toBe("turn-1");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("advances the turn id when a continuation reuses the existing message", () => {
|
|
473
|
+
const existing = turnStreamUpdateToAssistantMessage("turn-1", {
|
|
474
|
+
content: [{ type: "text", text: "first" }],
|
|
475
|
+
});
|
|
476
|
+
const updated = turnStreamUpdateToAssistantMessage(
|
|
477
|
+
"turn-2",
|
|
478
|
+
{ content: [{ type: "text", text: "second" }] },
|
|
479
|
+
existing,
|
|
480
|
+
);
|
|
481
|
+
expect(updated.id).toBe("turn-1-assistant");
|
|
482
|
+
expect(updated.metadata.custom.turnId).toBe("turn-2");
|
|
483
|
+
});
|
|
464
484
|
});
|
|
465
485
|
|
|
466
486
|
describe("repositoryItemsFromMessages", () => {
|
|
@@ -546,7 +566,7 @@ describe("convertTurnMessages", () => {
|
|
|
546
566
|
|
|
547
567
|
expect(result.messages[1]).toMatchObject({
|
|
548
568
|
role: "assistant",
|
|
549
|
-
metadata: { custom: { sandboxId: "sbx-123" } },
|
|
569
|
+
metadata: { custom: { sandboxId: "sbx-123", turnId: "turn-1" } },
|
|
550
570
|
});
|
|
551
571
|
});
|
|
552
572
|
|
|
@@ -601,6 +621,8 @@ describe("convertTurnMessages", () => {
|
|
|
601
621
|
{ type: "text", text: "first chunk" },
|
|
602
622
|
{ type: "text", text: " after approval" },
|
|
603
623
|
]);
|
|
624
|
+
// Artifacts on a merged message belong to the turn that wrote them last.
|
|
625
|
+
expect(assistant.metadata.custom.turnId).toBe("turn-2");
|
|
604
626
|
});
|
|
605
627
|
|
|
606
628
|
it("preserves requires-action on reload when a turn ends awaiting tool approval", async () => {
|
|
@@ -1074,8 +1074,14 @@ function projectHistoryTurns(
|
|
|
1074
1074
|
content,
|
|
1075
1075
|
record,
|
|
1076
1076
|
);
|
|
1077
|
-
|
|
1078
|
-
|
|
1077
|
+
// Continuation turns fold into the previous assistant message and overwrite this custom,
|
|
1078
|
+
// so a folded message reports the latest turn that contributed to it — the one whose
|
|
1079
|
+
// sandbox wrote the newest artifacts.
|
|
1080
|
+
const custom = {
|
|
1081
|
+
...baseCustom,
|
|
1082
|
+
turnId: record.id,
|
|
1083
|
+
...(record.sandboxId != null ? { sandboxId: record.sandboxId } : {}),
|
|
1084
|
+
};
|
|
1079
1085
|
|
|
1080
1086
|
if (record.userText) {
|
|
1081
1087
|
messages.push(
|
|
@@ -1732,7 +1738,7 @@ export function turnStreamUpdateToAssistantMessage(
|
|
|
1732
1738
|
unstable_annotations: [],
|
|
1733
1739
|
unstable_data: [],
|
|
1734
1740
|
steps: [],
|
|
1735
|
-
custom: update.metadata?.custom
|
|
1741
|
+
custom: { ...update.metadata?.custom, turnId },
|
|
1736
1742
|
},
|
|
1737
1743
|
};
|
|
1738
1744
|
}
|
|
@@ -10,7 +10,12 @@ const defaultAgentSpec: AgentSpec = {
|
|
|
10
10
|
instructions: "You are helpful.",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
function mockDraft(
|
|
13
|
+
function mockDraft(
|
|
14
|
+
id: string,
|
|
15
|
+
title: string | undefined,
|
|
16
|
+
updatedAt: string,
|
|
17
|
+
extras?: Partial<Session>,
|
|
18
|
+
): Session {
|
|
14
19
|
return {
|
|
15
20
|
id,
|
|
16
21
|
agentSpec: defaultAgentSpec,
|
|
@@ -18,6 +23,7 @@ function mockDraft(id: string, title: string | undefined, updatedAt: string): Se
|
|
|
18
23
|
createdAt: updatedAt,
|
|
19
24
|
updatedAt,
|
|
20
25
|
isMutable: true,
|
|
26
|
+
...extras,
|
|
21
27
|
};
|
|
22
28
|
}
|
|
23
29
|
|
|
@@ -33,10 +39,22 @@ function mockServer(partial: Partial<AgentChatServer>): AgentChatServer {
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
describe("createTrueFoundryDraftThreadListAdapter", () => {
|
|
36
|
-
it("lists
|
|
42
|
+
it("lists sessions with pagination cursor and does not filter isMutable client-side", async () => {
|
|
37
43
|
const listSessions = vi.fn().mockResolvedValue(
|
|
38
44
|
mockDraftListPage(
|
|
39
|
-
[
|
|
45
|
+
[
|
|
46
|
+
mockDraft("d1", "My draft", "2026-06-30T10:00:00.000Z"),
|
|
47
|
+
mockDraft("s1", "Named", "2026-06-30T09:00:00.000Z", {
|
|
48
|
+
isMutable: false,
|
|
49
|
+
agentName: "from-sdk",
|
|
50
|
+
agentSpec: undefined,
|
|
51
|
+
}),
|
|
52
|
+
mockDraft("s2", undefined, "2026-06-30T08:00:00.000Z", {
|
|
53
|
+
isMutable: false,
|
|
54
|
+
agentName: "untitled-agent",
|
|
55
|
+
agentSpec: undefined,
|
|
56
|
+
}),
|
|
57
|
+
],
|
|
40
58
|
"page-2",
|
|
41
59
|
),
|
|
42
60
|
);
|
|
@@ -61,7 +79,7 @@ describe("createTrueFoundryDraftThreadListAdapter", () => {
|
|
|
61
79
|
}),
|
|
62
80
|
);
|
|
63
81
|
expect(listSessions).toHaveBeenCalledWith(
|
|
64
|
-
expect.not.objectContaining({
|
|
82
|
+
expect.not.objectContaining({ agentId: expect.anything() }),
|
|
65
83
|
);
|
|
66
84
|
expect(result.threads).toEqual([
|
|
67
85
|
{
|
|
@@ -70,10 +88,45 @@ describe("createTrueFoundryDraftThreadListAdapter", () => {
|
|
|
70
88
|
title: "My draft",
|
|
71
89
|
lastMessageAt: new Date("2026-06-30T10:00:00.000Z"),
|
|
72
90
|
},
|
|
91
|
+
{
|
|
92
|
+
status: "regular",
|
|
93
|
+
remoteId: "s1",
|
|
94
|
+
title: "Named",
|
|
95
|
+
lastMessageAt: new Date("2026-06-30T09:00:00.000Z"),
|
|
96
|
+
custom: { agentName: "from-sdk" },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
status: "regular",
|
|
100
|
+
remoteId: "s2",
|
|
101
|
+
title: "untitled-agent",
|
|
102
|
+
lastMessageAt: new Date("2026-06-30T08:00:00.000Z"),
|
|
103
|
+
custom: { agentName: "untitled-agent" },
|
|
104
|
+
},
|
|
73
105
|
]);
|
|
74
106
|
expect(result.nextCursor).toBe("page-2");
|
|
75
107
|
});
|
|
76
108
|
|
|
109
|
+
it("forwards listSessionsAgentId as agentId", async () => {
|
|
110
|
+
const listSessions = vi.fn().mockResolvedValue(mockDraftListPage([]));
|
|
111
|
+
const server = mockServer({
|
|
112
|
+
listSessions,
|
|
113
|
+
createSession: vi.fn(),
|
|
114
|
+
getSession: vi.fn(),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const adapter = createTrueFoundryDraftThreadListAdapter({
|
|
118
|
+
server,
|
|
119
|
+
defaultAgentSpec,
|
|
120
|
+
listSessionsAgentId: "agent-x",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await adapter.list();
|
|
124
|
+
|
|
125
|
+
expect(listSessions).toHaveBeenCalledWith(
|
|
126
|
+
expect.objectContaining({ agentId: "agent-x" }),
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
77
130
|
it("creates a draft session on initialize", async () => {
|
|
78
131
|
const createSession = vi.fn().mockResolvedValue(
|
|
79
132
|
mockDraft("d-new", undefined, "2026-06-30T12:00:00.000Z"),
|
|
@@ -2,8 +2,11 @@ import type { RemoteThreadListAdapter } from "@assistant-ui/core";
|
|
|
2
2
|
|
|
3
3
|
import type { AgentChatServer } from "../server/types.js";
|
|
4
4
|
import type { AgentSpec } from "../server/types.js";
|
|
5
|
-
import { draftSessionTitle } from "./agentSpec.js";
|
|
6
5
|
import { sessionListStartTimestamp } from "../sessionListStartTimestamp.js";
|
|
6
|
+
import {
|
|
7
|
+
sessionDisplayTitle,
|
|
8
|
+
sessionToThreadMetadata,
|
|
9
|
+
} from "../sessionThreadMetadata.js";
|
|
7
10
|
|
|
8
11
|
const THREAD_LIST_PAGE_SIZE = 20;
|
|
9
12
|
|
|
@@ -11,27 +14,25 @@ export function createTrueFoundryDraftThreadListAdapter(options: {
|
|
|
11
14
|
server: AgentChatServer;
|
|
12
15
|
defaultAgentSpec: AgentSpec;
|
|
13
16
|
getAgentSpec?: () => AgentSpec;
|
|
17
|
+
/** When set, filters `listSessions` by this agent id. Omit for all chats. */
|
|
18
|
+
listSessionsAgentId?: string;
|
|
14
19
|
}): RemoteThreadListAdapter {
|
|
15
|
-
const { server, defaultAgentSpec, getAgentSpec } = options;
|
|
20
|
+
const { server, defaultAgentSpec, getAgentSpec, listSessionsAgentId } = options;
|
|
16
21
|
|
|
17
22
|
return {
|
|
18
23
|
async list({ after } = {}) {
|
|
19
24
|
const page = await server.listSessions({
|
|
25
|
+
...(listSessionsAgentId != null ? { agentId: listSessionsAgentId } : {}),
|
|
20
26
|
limit: THREAD_LIST_PAGE_SIZE,
|
|
21
27
|
pageToken: after,
|
|
22
28
|
startTimestamp: sessionListStartTimestamp(),
|
|
23
29
|
});
|
|
24
|
-
const threads = page.data
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
title: draft.title,
|
|
31
|
-
agentSpec: draft.agentSpec ?? defaultAgentSpec,
|
|
32
|
-
}),
|
|
33
|
-
lastMessageAt: new Date(draft.updatedAt),
|
|
34
|
-
}));
|
|
30
|
+
const threads = page.data.map((session) =>
|
|
31
|
+
sessionToThreadMetadata(
|
|
32
|
+
session,
|
|
33
|
+
sessionDisplayTitle(session, defaultAgentSpec),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
35
36
|
return {
|
|
36
37
|
threads,
|
|
37
38
|
nextCursor: page.nextPageToken ?? undefined,
|
|
@@ -47,15 +48,10 @@ export function createTrueFoundryDraftThreadListAdapter(options: {
|
|
|
47
48
|
|
|
48
49
|
async fetch(remoteId) {
|
|
49
50
|
const draft = await server.getSession({ sessionId: remoteId });
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
title: draft.title,
|
|
55
|
-
agentSpec: draft.agentSpec ?? defaultAgentSpec,
|
|
56
|
-
}),
|
|
57
|
-
lastMessageAt: new Date(draft.updatedAt),
|
|
58
|
-
};
|
|
51
|
+
return sessionToThreadMetadata(
|
|
52
|
+
draft,
|
|
53
|
+
sessionDisplayTitle(draft, defaultAgentSpec),
|
|
54
|
+
);
|
|
59
55
|
},
|
|
60
56
|
|
|
61
57
|
async rename() {},
|
package/src/hooks.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useMemo } from "react";
|
|
4
|
-
import { useAui } from "@assistant-ui/store";
|
|
4
|
+
import { useAui, useAuiState } from "@assistant-ui/store";
|
|
5
5
|
|
|
6
|
+
import type { TrueFoundryMessageCustomMetadata } from "./messageCustomMetadata.js";
|
|
6
7
|
import {
|
|
7
8
|
EMPTY_DRAFT_EXTRAS,
|
|
8
9
|
trueFoundryExtras,
|
|
@@ -86,10 +87,29 @@ export const useTrueFoundryResumeMcpAuth = () => {
|
|
|
86
87
|
export const useTrueFoundrySandboxId = (): string | undefined =>
|
|
87
88
|
trueFoundryExtras.use((e) => e.sandboxId, undefined);
|
|
88
89
|
|
|
89
|
-
/**
|
|
90
|
+
/** Turn that produced the message being rendered. Only defined inside a message scope. */
|
|
91
|
+
export const useTrueFoundryTurnId = (): string | undefined =>
|
|
92
|
+
useAuiState(
|
|
93
|
+
(state) =>
|
|
94
|
+
(state.message.metadata?.custom as TrueFoundryMessageCustomMetadata | undefined)
|
|
95
|
+
?.turnId,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns a function to download a file the current turn wrote to its sandbox. Must be called
|
|
100
|
+
* from a message scope, since the artifact belongs to the turn that rendered it.
|
|
101
|
+
*/
|
|
90
102
|
export const useTrueFoundryDownloadSandboxFile = () => {
|
|
91
103
|
const aui = useAui();
|
|
92
|
-
|
|
104
|
+
const turnId = useTrueFoundryTurnId();
|
|
105
|
+
return (path: string) => {
|
|
106
|
+
if (turnId == null) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"Downloading a sandbox file requires a message scope to resolve its turn.",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return trueFoundryExtras.get(aui).downloadSandboxFile({ turnId, path });
|
|
112
|
+
};
|
|
93
113
|
};
|
|
94
114
|
|
|
95
115
|
/** Returns a function to cancel the current run from any render context. */
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type TrueFoundryMessageCustomMetadata = {
|
|
|
10
10
|
pendingMcpAuth?: true;
|
|
11
11
|
mcpServers?: McpAuthRequiredEvent["mcpServers"];
|
|
12
12
|
sandboxId?: string;
|
|
13
|
+
/** Turn that produced this message. Scopes artifact downloads to their own turn. */
|
|
14
|
+
turnId?: string;
|
|
13
15
|
[TOOL_APPROVAL_THREAD_ID_CUSTOM_KEY]?: string;
|
|
14
16
|
[TOOL_RESPONSE_THREAD_ID_CUSTOM_KEY]?: string;
|
|
15
17
|
};
|
|
@@ -227,7 +227,7 @@ export function createTrueFoundryChatServer<
|
|
|
227
227
|
pageToken: req?.pageToken,
|
|
228
228
|
startTimestamp: req?.startTimestamp,
|
|
229
229
|
endTimestamp: req?.endTimestamp,
|
|
230
|
-
...(req?.
|
|
230
|
+
...(req?.agentId != null ? { agentName: req.agentId } : {}),
|
|
231
231
|
});
|
|
232
232
|
const result = await toListResult(page, (s) => toSession<TSpec>(s));
|
|
233
233
|
for (const session of result.data) {
|
|
@@ -350,11 +350,12 @@ export function createTrueFoundryChatServer<
|
|
|
350
350
|
) as AsyncIterable<TurnStreamData>;
|
|
351
351
|
},
|
|
352
352
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
// The gateway addresses sandboxes directly, so the turn is carried for hosts that need it
|
|
354
|
+
// but is not part of this route.
|
|
355
|
+
async downloadSandboxFile({ sandboxId, path }) {
|
|
356
|
+
const response = await privateClient.downloadSandboxFile(sandboxId, {
|
|
357
|
+
path,
|
|
358
|
+
});
|
|
358
359
|
return await response.blob();
|
|
359
360
|
},
|
|
360
361
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
agentSpecFromCpManifest,
|
|
4
5
|
buildSaveAgentManifest,
|
|
5
6
|
normalizeAgents,
|
|
6
7
|
normalizeAgentSkills,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
saveAgent,
|
|
11
12
|
SAVE_AGENT_COLLABORATORS,
|
|
12
13
|
SAVE_AGENT_METADATA_TAGS,
|
|
14
|
+
toCamelCaseDeep,
|
|
13
15
|
toSnakeCaseDeep,
|
|
14
16
|
} from "./cp.js";
|
|
15
17
|
|
|
@@ -273,16 +275,175 @@ describe("normalizeMcpServers", () => {
|
|
|
273
275
|
});
|
|
274
276
|
|
|
275
277
|
describe("normalizeAgents", () => {
|
|
276
|
-
it("maps name
|
|
278
|
+
it("maps name, agentId, and agentSpec from latestVersionDetails.manifest", () => {
|
|
277
279
|
expect(
|
|
278
280
|
normalizeAgents({
|
|
279
281
|
data: [
|
|
280
|
-
{
|
|
282
|
+
{
|
|
283
|
+
id: "ag_1",
|
|
284
|
+
name: "ask-ai-agent",
|
|
285
|
+
latestVersionDetails: {
|
|
286
|
+
manifest: {
|
|
287
|
+
type: "truefoundry-agent",
|
|
288
|
+
name: "ask-ai-agent",
|
|
289
|
+
model: {
|
|
290
|
+
name: "openai-main/gpt-4.1",
|
|
291
|
+
params: {
|
|
292
|
+
max_tokens: 8192,
|
|
293
|
+
reasoning_effort: "medium",
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
instructions: "Be helpful",
|
|
297
|
+
config: {
|
|
298
|
+
iteration_limit: 50,
|
|
299
|
+
ask_user_questions: { enabled: true },
|
|
300
|
+
},
|
|
301
|
+
skills: [
|
|
302
|
+
{
|
|
303
|
+
type: "truefoundry-skills-registry",
|
|
304
|
+
fqn: "agent-skill:tfy/skills/web:1",
|
|
305
|
+
preload: true,
|
|
306
|
+
config: { timeout_ms: 1000 },
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
mcp_servers: [
|
|
310
|
+
{
|
|
311
|
+
name: "gmail",
|
|
312
|
+
enable_tools: ["@read-only"],
|
|
313
|
+
preload: true,
|
|
314
|
+
config: { locale: "en" },
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
},
|
|
281
320
|
{ name: "" },
|
|
282
321
|
{},
|
|
322
|
+
{
|
|
323
|
+
name: "try-only",
|
|
324
|
+
// no manifest → Try still works; Edit hidden
|
|
325
|
+
},
|
|
283
326
|
],
|
|
284
327
|
}),
|
|
285
|
-
).toEqual([
|
|
328
|
+
).toEqual([
|
|
329
|
+
{
|
|
330
|
+
name: "ask-ai-agent",
|
|
331
|
+
agentId: "ag_1",
|
|
332
|
+
agentSpec: {
|
|
333
|
+
model: {
|
|
334
|
+
name: "openai-main/gpt-4.1",
|
|
335
|
+
params: { maxTokens: 8192, reasoningEffort: "medium" },
|
|
336
|
+
},
|
|
337
|
+
instructions: "Be helpful",
|
|
338
|
+
config: {
|
|
339
|
+
iterationLimit: 50,
|
|
340
|
+
askUserQuestions: { enabled: true },
|
|
341
|
+
},
|
|
342
|
+
skills: [
|
|
343
|
+
{
|
|
344
|
+
id: "agent-skill:tfy/skills/web:1",
|
|
345
|
+
name: "agent-skill:tfy/skills/web:1",
|
|
346
|
+
preload: true,
|
|
347
|
+
config: { timeoutMs: 1000 },
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
mcpServers: [
|
|
351
|
+
{
|
|
352
|
+
id: "gmail",
|
|
353
|
+
name: "gmail",
|
|
354
|
+
enableTools: ["@read-only"],
|
|
355
|
+
preload: true,
|
|
356
|
+
config: { locale: "en" },
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{ name: "try-only", agentId: "try-only" },
|
|
362
|
+
]);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe("toCamelCaseDeep", () => {
|
|
367
|
+
it("converts nested snake_case keys", () => {
|
|
368
|
+
expect(
|
|
369
|
+
toCamelCaseDeep({
|
|
370
|
+
max_tokens: 8192,
|
|
371
|
+
reasoning_effort: "medium",
|
|
372
|
+
nested: { iteration_limit: 50 },
|
|
373
|
+
}),
|
|
374
|
+
).toEqual({
|
|
375
|
+
maxTokens: 8192,
|
|
376
|
+
reasoningEffort: "medium",
|
|
377
|
+
nested: { iterationLimit: 50 },
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("agentSpecFromCpManifest", () => {
|
|
383
|
+
it("returns undefined when model.name is missing", () => {
|
|
384
|
+
expect(agentSpecFromCpManifest({ type: "truefoundry-agent" })).toBeUndefined();
|
|
385
|
+
expect(agentSpecFromCpManifest(null)).toBeUndefined();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("preserves mount runtime fields and agent config for Edit → Save", () => {
|
|
389
|
+
const spec = agentSpecFromCpManifest({
|
|
390
|
+
type: "truefoundry-agent",
|
|
391
|
+
name: "saved",
|
|
392
|
+
model: { name: "openai-main/gpt-4.1" },
|
|
393
|
+
config: { iteration_limit: 25 },
|
|
394
|
+
skills: [
|
|
395
|
+
{
|
|
396
|
+
type: "truefoundry-skills-registry",
|
|
397
|
+
fqn: "agent-skill:tfy/skills/web:1",
|
|
398
|
+
preload: false,
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
mcp_servers: [
|
|
402
|
+
{
|
|
403
|
+
name: "gmail",
|
|
404
|
+
enable_tools: ["@read-only"],
|
|
405
|
+
preload: true,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
expect(spec).toEqual({
|
|
411
|
+
model: { name: "openai-main/gpt-4.1" },
|
|
412
|
+
config: { iterationLimit: 25 },
|
|
413
|
+
skills: [
|
|
414
|
+
{
|
|
415
|
+
id: "agent-skill:tfy/skills/web:1",
|
|
416
|
+
name: "agent-skill:tfy/skills/web:1",
|
|
417
|
+
preload: false,
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
mcpServers: [
|
|
421
|
+
{
|
|
422
|
+
id: "gmail",
|
|
423
|
+
name: "gmail",
|
|
424
|
+
enableTools: ["@read-only"],
|
|
425
|
+
preload: true,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const saved = buildSaveAgentManifest("saved", spec!);
|
|
431
|
+
expect(saved.config).toEqual({ iteration_limit: 25 });
|
|
432
|
+
expect(saved.mcp_servers).toEqual([
|
|
433
|
+
{
|
|
434
|
+
type: "truefoundry-mcp-registry",
|
|
435
|
+
name: "gmail",
|
|
436
|
+
enable_tools: ["@read-only"],
|
|
437
|
+
preload: true,
|
|
438
|
+
},
|
|
439
|
+
]);
|
|
440
|
+
expect(saved.skills).toEqual([
|
|
441
|
+
{
|
|
442
|
+
type: "truefoundry-skills-registry",
|
|
443
|
+
fqn: "agent-skill:tfy/skills/web:1",
|
|
444
|
+
preload: false,
|
|
445
|
+
},
|
|
446
|
+
]);
|
|
286
447
|
});
|
|
287
448
|
});
|
|
288
449
|
|
|
@@ -432,9 +593,11 @@ describe("saveAgent", () => {
|
|
|
432
593
|
}),
|
|
433
594
|
}),
|
|
434
595
|
);
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
596
|
+
const [, init] = fetchMock.mock.calls[0] as unknown as [
|
|
597
|
+
string,
|
|
598
|
+
RequestInit,
|
|
599
|
+
];
|
|
600
|
+
const body = JSON.parse(String(init.body));
|
|
438
601
|
expect(body.manifest.type).toBe("truefoundry-agent");
|
|
439
602
|
expect(body.manifest.name).toBe("my-agent");
|
|
440
603
|
expect(body.manifest.model).toEqual({ name: "openai-main/gpt-4.1" });
|