@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.
- package/dist/{chunk-O7OIV5IZ.js → chunk-G4TO3HAP.js} +99 -20
- package/dist/chunk-G4TO3HAP.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/plugins/truefoundry-agent-server-adapter/index.d.ts +10 -3
- package/dist/plugins/truefoundry-agent-server-adapter/index.js +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/{types-CQnbe6Qh.d.ts → types-CUUft1YT.d.ts} +116 -5
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/plugins/truefoundry-agent-server-adapter/cp.test.ts +235 -6
- package/src/plugins/truefoundry-agent-server-adapter/cp.ts +163 -15
- package/src/plugins/truefoundry-agent-server-adapter/createTrueFoundryAgentUIServer.ts +2 -2
- package/src/plugins/truefoundry-agent-server-adapter/index.ts +26 -3
- package/src/plugins/truefoundry-agent-server-adapter/types.ts +10 -0
- package/src/plugins/truefoundry-agent-server-adapter/types.typecheck.ts +14 -0
- package/src/server/index.ts +8 -0
- package/src/server/types.ts +133 -4
- package/dist/chunk-O7OIV5IZ.js.map +0 -1
|
@@ -314,6 +314,7 @@ interface AgentSpec<TModel extends Model = Model, TSkill extends SkillMount = Sk
|
|
|
314
314
|
config?: TConfig;
|
|
315
315
|
instructions?: string;
|
|
316
316
|
variables?: Record<string, string>;
|
|
317
|
+
description?: string;
|
|
317
318
|
}
|
|
318
319
|
interface Session<TSpec extends AgentSpec = AgentSpec> {
|
|
319
320
|
id: string;
|
|
@@ -346,10 +347,12 @@ type PageParams = {
|
|
|
346
347
|
pageToken?: string;
|
|
347
348
|
};
|
|
348
349
|
interface ListSessionsParams extends PageParams {
|
|
349
|
-
/** Host-owned agent identity filter.
|
|
350
|
+
/** Host-owned agent identity filter. Omit for all sessions (current user). */
|
|
350
351
|
agentId?: string;
|
|
351
|
-
/**
|
|
352
|
+
/** Inclusive lower bound on session activity (ISO-8601). */
|
|
352
353
|
startTimestamp?: string;
|
|
354
|
+
/** Inclusive upper bound on session activity (ISO-8601). */
|
|
355
|
+
endTimestamp?: string;
|
|
353
356
|
}
|
|
354
357
|
type PreviousTurnIdInput = "auto" | "none" | string;
|
|
355
358
|
type UserMessageContent = string | Array<{
|
|
@@ -386,11 +389,25 @@ type TurnInputItem = UserMessage | UserToolApprovalEvent | UserToolResponseEvent
|
|
|
386
389
|
type TurnStateRunning = {
|
|
387
390
|
status: "running";
|
|
388
391
|
};
|
|
392
|
+
/**
|
|
393
|
+
* Aggregated token metrics on a finished turn (`turn.done.state.metrics`).
|
|
394
|
+
* Host maps wire snake_case (`total_input_tokens`, …) → camelCase here.
|
|
395
|
+
*/
|
|
396
|
+
interface TurnDoneMetrics {
|
|
397
|
+
totalInputTokens: number;
|
|
398
|
+
totalOutputTokens: number;
|
|
399
|
+
totalTokens: number;
|
|
400
|
+
totalCacheReadTokens: number;
|
|
401
|
+
totalCacheWriteTokens: number;
|
|
402
|
+
totalReasoningTokens: number;
|
|
403
|
+
}
|
|
389
404
|
type TurnStateDone = {
|
|
390
405
|
status: "done";
|
|
391
406
|
output?: unknown;
|
|
392
407
|
requiredActions?: ActionRequiredEvent[];
|
|
393
408
|
completedAt: string;
|
|
409
|
+
/** Present when the host reports per-turn token totals. */
|
|
410
|
+
metrics?: TurnDoneMetrics;
|
|
394
411
|
};
|
|
395
412
|
type TurnStateCancelled = {
|
|
396
413
|
status: "cancelled";
|
|
@@ -491,6 +508,8 @@ interface SaveAgentRequest<TSpec extends AgentSpec = AgentSpec> {
|
|
|
491
508
|
interface SaveAgentResult {
|
|
492
509
|
/** Immutable id allocated by the host registry. */
|
|
493
510
|
agentId?: string;
|
|
511
|
+
/** Version id allocated by the host registry for this save. */
|
|
512
|
+
versionId?: string;
|
|
494
513
|
/** Timestamp returned when the active mutable session was updated. */
|
|
495
514
|
sessionUpdatedAt?: string;
|
|
496
515
|
}
|
|
@@ -827,18 +846,110 @@ interface CatalogServer<TModelCatalog extends ModelCatalogServer = ModelCatalogS
|
|
|
827
846
|
sandboxCatalog?: TSandboxCatalog;
|
|
828
847
|
}
|
|
829
848
|
/**
|
|
830
|
-
*
|
|
849
|
+
* Published agent identity + spec for the agent-detail Overview (read-only).
|
|
850
|
+
* Host widens `TSpec` (and may extend this DTO) for mounts / config / extras.
|
|
851
|
+
*/
|
|
852
|
+
interface AgentDetail<TSpec extends AgentSpec = AgentSpec> {
|
|
853
|
+
agentId: string;
|
|
854
|
+
/** Display name (e.g. "release-notes-writer"). */
|
|
855
|
+
name: string;
|
|
856
|
+
agentSpec: TSpec;
|
|
857
|
+
}
|
|
858
|
+
/** Stream vs non-stream bodies for one language on the Use In Code tab. */
|
|
859
|
+
interface CodeSnippetSampleCode {
|
|
860
|
+
stream: string;
|
|
861
|
+
nonStream: string;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* One language row for Use In Code.
|
|
865
|
+
* Host maps wire `sample_code` / `non_stream` → camelCase here.
|
|
866
|
+
*/
|
|
867
|
+
interface CodeSnippet<TSample extends CodeSnippetSampleCode = CodeSnippetSampleCode> {
|
|
868
|
+
/** Sidebar label (e.g. "TypeScript"). */
|
|
869
|
+
labelName: string;
|
|
870
|
+
/** Highlighter / tab id (e.g. "typescript"). */
|
|
871
|
+
language: string;
|
|
872
|
+
icon?: string;
|
|
873
|
+
sampleCode: TSample;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Aggregated session metrics for the sessions list sidebar.
|
|
877
|
+
* Host maps wire snake_case (`total_turns`, `total_cost_in_usd`, …) → camelCase.
|
|
878
|
+
*/
|
|
879
|
+
interface SessionListMetrics {
|
|
880
|
+
totalTurns: number;
|
|
881
|
+
totalCostInUsd: number;
|
|
882
|
+
totalDurationMs: number;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* One row in the Agent Sessions list (left pane).
|
|
886
|
+
*
|
|
887
|
+
* Binding: `agentName` → named / immutable agent; `agentSpec` → mutable / draft.
|
|
888
|
+
* Host may send one, both, or neither depending on how the session was created.
|
|
889
|
+
*/
|
|
890
|
+
interface SessionListEntry<TSpec extends AgentSpec = AgentSpec> {
|
|
891
|
+
id: string;
|
|
892
|
+
title?: string | null;
|
|
893
|
+
createdAt: string;
|
|
894
|
+
updatedAt: string;
|
|
895
|
+
lastActivityAt: string;
|
|
896
|
+
metrics: SessionListMetrics;
|
|
897
|
+
/** Present when bound to a published (immutable) agent. */
|
|
898
|
+
agentName?: string | null;
|
|
899
|
+
/** Present when bound to a mutable / draft agent spec. */
|
|
900
|
+
agentSpec?: TSpec;
|
|
901
|
+
}
|
|
902
|
+
/** Params for `AgentSessionsServer.listSessionEvents` (session event timeline). */
|
|
903
|
+
interface ListSessionEventsParams extends Pick<PageParams, "limit" | "pageToken"> {
|
|
904
|
+
sessionId: string;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Optional plug-in for agent-detail UI: Overview, Use In Code, sessions list,
|
|
908
|
+
* and per-session event timeline. Omit `sessions` on `AgentUIServerPort` when
|
|
909
|
+
* the host has no agent-detail surface.
|
|
910
|
+
*
|
|
911
|
+
* Read-only — create/update/delete stay on `AgentChatServer` / `AgentBuilderServer`.
|
|
912
|
+
*/
|
|
913
|
+
interface AgentSessionsServer<TSpec extends AgentSpec = AgentSpec, TDetail extends AgentDetail<TSpec> = AgentDetail<TSpec>, TSnippet extends CodeSnippet = CodeSnippet, TListEntry extends SessionListEntry<TSpec> = SessionListEntry<TSpec>, TList extends ListSessionsParams = ListSessionsParams> {
|
|
914
|
+
/** Fetch published agent details by id for the Overview tab. */
|
|
915
|
+
getAgent(req: {
|
|
916
|
+
agentId: string;
|
|
917
|
+
}): Promise<TDetail>;
|
|
918
|
+
/** Fetch Use In Code snippets for the agent (one row per language). */
|
|
919
|
+
getCodeSnippets(req: {
|
|
920
|
+
agentId: string;
|
|
921
|
+
}): Promise<TSnippet[]>;
|
|
922
|
+
/**
|
|
923
|
+
* List sessions for the current user. Pass `agentId` to scope to one agent;
|
|
924
|
+
* omit for all sessions. Use `startTimestamp` / `endTimestamp` for date filters.
|
|
925
|
+
*/
|
|
926
|
+
listSessions(req?: TList): Promise<ListResult<TListEntry>>;
|
|
927
|
+
/**
|
|
928
|
+
* Fetch the session event timeline (right pane). Paginate with `pageToken`
|
|
929
|
+
* until exhausted; rebuild turns from `turn.created` / `turn.done` +
|
|
930
|
+
* nested `TurnEvent`s. Per-turn token metrics live on `turn.done.state.metrics`.
|
|
931
|
+
*/
|
|
932
|
+
listSessionEvents(req: ListSessionEventsParams): Promise<ListResult<SessionEventItem>>;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Composed host port: chat + builder + optional settings catalog + optional
|
|
936
|
+
* agent-detail / sessions shell.
|
|
831
937
|
*
|
|
832
938
|
* `catalog` is optional — if the host passes it, settings UI can call
|
|
833
939
|
* `useCatalogServer()` / show modelCatalog, connectorCatalog, and skillCatalog;
|
|
834
940
|
* if omitted, those surfaces stay hidden.
|
|
835
941
|
*
|
|
942
|
+
* `sessions` is optional — if the host passes it, agent-detail UI can call
|
|
943
|
+
* `useAgentSessionsServer()` / Overview + sessions under an agent; if omitted,
|
|
944
|
+
* that surface stays hidden.
|
|
945
|
+
*
|
|
836
946
|
* trueforge-ui re-exports this as `AgentUIServer`.
|
|
837
947
|
*/
|
|
838
|
-
type AgentUIServerPort<TChat extends AgentChatServer = AgentChatServer, TBuilder extends AgentBuilderServer = AgentBuilderServer, TCatalog extends CatalogServer = CatalogServer> = TChat & TBuilder & {
|
|
948
|
+
type AgentUIServerPort<TChat extends AgentChatServer = AgentChatServer, TBuilder extends AgentBuilderServer = AgentBuilderServer, TCatalog extends CatalogServer = CatalogServer, TSessions extends AgentSessionsServer = AgentSessionsServer> = TChat & TBuilder & {
|
|
839
949
|
catalog?: TCatalog;
|
|
950
|
+
sessions?: TSessions;
|
|
840
951
|
};
|
|
841
952
|
/** Host-facing alias used by trueforge-ui. */
|
|
842
953
|
type AgentUIServer = AgentUIServerPort;
|
|
843
954
|
|
|
844
|
-
export type {
|
|
955
|
+
export type { ListSessionEventsParams as $, AgentSpec as A, ConnectorAuthPublicNone as B, CatalogServer as C, ConnectorAuthPublicOAuth as D, ConnectorAuthType as E, ConnectorAuthenticationResult as F, ConnectorBase as G, ConnectorCatalogEntry as H, ConnectorCatalogServer as I, ConnectorConfigBase as J, ConnectorSelectorEntry as K, ConnectorState as L, McpAuthRequiredEvent as M, CreateConnectorRequest as N, CreateModelProviderRequest as O, CreateSandboxProviderRequest as P, CreateSandboxRequest as Q, CreateSessionRequest as R, Session as S, TurnEvent as T, UserToolResponseEvent as U, CreateSkillRequest as V, CreateSkillRequestBase as W, DefinedSkill as X, GithubSkill as Y, ImportGithubSkillRequest as Z, ListResult as _, AgentChatServer as a, McpInitializeEvent as a$, ListSessionsOrder as a0, ListSessionsParams as a1, McpServerMount as a2, Model as a3, ModelCatalogServer as a4, ModelEntry as a5, ModelMessageEvent as a6, ModelParams as a7, ModelProperties as a8, ModelProviderBase as a9, SessionListMetrics as aA, SkillBase as aB, SkillCatalogEntry as aC, SkillCatalogServer as aD, SkillConfigBase as aE, SkillMount as aF, SkillSelectorEntry as aG, ToolApprovalRequiredEvent as aH, ToolBase as aI, ToolCall as aJ, ToolResponseRequiredEvent as aK, TurnDoneMetrics as aL, TurnState as aM, TurnStateDone as aN, TurnStreamData as aO, TurnStreamingEvent as aP, UpdateConnectorRequest as aQ, UpdateModelProviderRequest as aR, UpdateSandboxProviderRequest as aS, UpdateSandboxRequest as aT, UpdateSessionRequest as aU, UserMessage as aV, DeltaEvents as aW, ActionRequiredEvent as aX, AgentInfo as aY, AgentParent as aZ, ChunkDeltaToolCall as a_, ModelProviderCatalogEntry as aa, ModelProviderConfigBase as ab, ModelSelection as ac, ModelSelectorEntry as ad, PageParams as ae, PreviousTurnIdInput as af, ProviderEntry as ag, ProviderType as ah, RegistrySkill as ai, SandboxBase as aj, SandboxCatalogEntry as ak, SandboxCatalogServer as al, SandboxConfig as am, SandboxCreatedEvent as an, SandboxProviderBase as ao, SandboxProviderCatalogEntry as ap, SandboxProviderConfig as aq, SandboxProviderListEntry as ar, SandboxSnapshotSyncStatus as as, SaveAgentRequest as at, SaveAgentResult as au, SearchAgentSelectorParams as av, SearchAgentsParams as aw, SelectRegistrySkillRequest as ax, SessionEventItem as ay, SessionListEntry as az, ThreadCreatedEvent as b, McpServerAuthInfo as b0, ModelMessageContentPart as b1, ModelMessageDeltaEvent as b2, ThreadDoneEvent as b3, ToolCallFunction as b4, ToolCallRef as b5, ToolInfo as b6, ToolResponseEvent as b7, TurnCreatedEvent as b8, TurnDoneEvent as b9, TurnStateCancelled as ba, TurnStateError as bb, TurnStateRunning as bc, UserMessageContent as bd, UserToolApprovalEvent as c, Turn as d, TurnInputItem as e, AgentBuilderCapabilitiesResponse as f, AgentBuilderServer as g, AgentCapabilityConfig as h, AgentDetail as i, AgentLibraryEntry as j, AgentRuntimeConfig as k, AgentSelectorEntry as l, AgentSessionsServer as m, AgentSkill as n, AgentUIServer as o, AgentUIServerPort as p, ApprovalDecision as q, AuthenticateConnectorRequest as r, CodeSnippet as s, CodeSnippetSampleCode as t, ConnectorAuth as u, ConnectorAuthApiKey as v, ConnectorAuthNone as w, ConnectorAuthOAuth as x, ConnectorAuthPublic as y, ConnectorAuthPublicApiKey as z };
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -93,6 +93,7 @@ export type {
|
|
|
93
93
|
Session,
|
|
94
94
|
Turn,
|
|
95
95
|
TurnState,
|
|
96
|
+
TurnDoneMetrics,
|
|
96
97
|
TurnStateDone,
|
|
97
98
|
TurnInputItem,
|
|
98
99
|
AgentSpec,
|
|
@@ -158,6 +159,13 @@ export type {
|
|
|
158
159
|
UpdateSandboxProviderRequest,
|
|
159
160
|
SandboxCatalogServer,
|
|
160
161
|
CatalogServer,
|
|
162
|
+
AgentDetail,
|
|
163
|
+
CodeSnippetSampleCode,
|
|
164
|
+
CodeSnippet,
|
|
165
|
+
SessionListMetrics,
|
|
166
|
+
SessionListEntry,
|
|
167
|
+
ListSessionEventsParams,
|
|
168
|
+
AgentSessionsServer,
|
|
161
169
|
AgentUIServerPort,
|
|
162
170
|
AgentUIServer,
|
|
163
171
|
ProviderEntry,
|
|
@@ -208,6 +216,8 @@ export {
|
|
|
208
216
|
type TfyConnectorSelectorEntry,
|
|
209
217
|
type TfyAgentSelectorEntry,
|
|
210
218
|
type TfyAgentSpec,
|
|
219
|
+
type TfyCollaborator,
|
|
220
|
+
type TfySaveAgentResult,
|
|
211
221
|
type TfySkillMount,
|
|
212
222
|
type TfyMcpServerMount,
|
|
213
223
|
type TfyModelParams,
|
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
normalizeMcpServers,
|
|
10
10
|
resolveGatewayURL,
|
|
11
11
|
saveAgent,
|
|
12
|
+
saveAgentResultFromCp,
|
|
12
13
|
SAVE_AGENT_COLLABORATORS,
|
|
13
|
-
|
|
14
|
+
SAVE_AGENT_TAGS,
|
|
14
15
|
toCamelCaseDeep,
|
|
15
16
|
toSnakeCaseDeep,
|
|
16
17
|
} from "./cp.js";
|
|
@@ -467,7 +468,7 @@ describe("toSnakeCaseDeep", () => {
|
|
|
467
468
|
});
|
|
468
469
|
|
|
469
470
|
describe("buildSaveAgentManifest", () => {
|
|
470
|
-
it("
|
|
471
|
+
it("spreads the snake-cased spec and defaults missing tags / collaborators", () => {
|
|
471
472
|
const manifest = buildSaveAgentManifest("my-agent", {
|
|
472
473
|
model: {
|
|
473
474
|
name: "ai-foundry/claude-sonnet-4-6",
|
|
@@ -502,7 +503,7 @@ describe("buildSaveAgentManifest", () => {
|
|
|
502
503
|
name: "ai-foundry/claude-sonnet-4-6",
|
|
503
504
|
params: { max_tokens: 8192, reasoning_effort: "medium" },
|
|
504
505
|
},
|
|
505
|
-
|
|
506
|
+
tags: { ...SAVE_AGENT_TAGS },
|
|
506
507
|
collaborators: [...SAVE_AGENT_COLLABORATORS],
|
|
507
508
|
instructions: "Be helpful",
|
|
508
509
|
config: {
|
|
@@ -527,6 +528,45 @@ describe("buildSaveAgentManifest", () => {
|
|
|
527
528
|
});
|
|
528
529
|
});
|
|
529
530
|
|
|
531
|
+
it("keeps host tags, collaborators, and extra spec fields", () => {
|
|
532
|
+
const manifest = buildSaveAgentManifest("named", {
|
|
533
|
+
model: {
|
|
534
|
+
name: "openai-main/gpt-4.1",
|
|
535
|
+
params: { maxTokens: 4096, temperature: 0.2 },
|
|
536
|
+
},
|
|
537
|
+
description: "My agent",
|
|
538
|
+
instructions: "Be concise",
|
|
539
|
+
variables: { city: "Berlin" },
|
|
540
|
+
messages: [{ role: "user", content: "Hello {{city}}" }],
|
|
541
|
+
responseFormat: {
|
|
542
|
+
type: "json_schema",
|
|
543
|
+
jsonSchema: { name: "answer", schema: { type: "object" } },
|
|
544
|
+
},
|
|
545
|
+
tags: { env: "test", owner: "platform" },
|
|
546
|
+
collaborators: [
|
|
547
|
+
{ subject: "user:ada@example.com", roleId: "agent-manager" },
|
|
548
|
+
],
|
|
549
|
+
} as never);
|
|
550
|
+
|
|
551
|
+
expect(manifest.description).toBe("My agent");
|
|
552
|
+
expect(manifest.tags).toEqual({ env: "test", owner: "platform" });
|
|
553
|
+
expect(manifest.collaborators).toEqual([
|
|
554
|
+
{ subject: "user:ada@example.com", role_id: "agent-manager" },
|
|
555
|
+
]);
|
|
556
|
+
expect(manifest.variables).toEqual({ city: "Berlin" });
|
|
557
|
+
expect(manifest.messages).toEqual([
|
|
558
|
+
{ role: "user", content: "Hello {{city}}" },
|
|
559
|
+
]);
|
|
560
|
+
expect(manifest.response_format).toEqual({
|
|
561
|
+
type: "json_schema",
|
|
562
|
+
json_schema: { name: "answer", schema: { type: "object" } },
|
|
563
|
+
});
|
|
564
|
+
expect(manifest.model).toEqual({
|
|
565
|
+
name: "openai-main/gpt-4.1",
|
|
566
|
+
params: { max_tokens: 4096, temperature: 0.2 },
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
530
570
|
it("normalizes UI catalog mounts and defaults enable_tools / preload", () => {
|
|
531
571
|
const manifest = buildSaveAgentManifest("draft-save", {
|
|
532
572
|
model: { name: "openai-main/gpt-4.1" },
|
|
@@ -572,7 +612,7 @@ describe("buildSaveAgentManifest", () => {
|
|
|
572
612
|
describe("saveAgent", () => {
|
|
573
613
|
it("PUTs { manifest } to /api/svc/v1/agents", async () => {
|
|
574
614
|
const fetchMock = vi.fn(async () =>
|
|
575
|
-
Response.json({ id: "ag_1", name: "my-agent" }),
|
|
615
|
+
Response.json({ data: { id: "ag_1", name: "my-agent" } }),
|
|
576
616
|
);
|
|
577
617
|
vi.stubGlobal("fetch", fetchMock);
|
|
578
618
|
|
|
@@ -581,10 +621,11 @@ describe("saveAgent", () => {
|
|
|
581
621
|
{
|
|
582
622
|
agentName: "my-agent",
|
|
583
623
|
agentSpec: { model: { name: "openai-main/gpt-4.1" } },
|
|
624
|
+
intent: "create",
|
|
584
625
|
},
|
|
585
626
|
);
|
|
586
627
|
|
|
587
|
-
expect(result).toEqual({
|
|
628
|
+
expect(result).toEqual({ agentId: "ag_1" });
|
|
588
629
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
589
630
|
"https://cp.example/api/svc/v1/agents",
|
|
590
631
|
expect.objectContaining({
|
|
@@ -604,7 +645,195 @@ describe("saveAgent", () => {
|
|
|
604
645
|
expect(body.manifest.type).toBe("truefoundry-agent");
|
|
605
646
|
expect(body.manifest.name).toBe("my-agent");
|
|
606
647
|
expect(body.manifest.model).toEqual({ name: "openai-main/gpt-4.1" });
|
|
607
|
-
expect(body.manifest.
|
|
648
|
+
expect(body.manifest.tags).toEqual(SAVE_AGENT_TAGS);
|
|
608
649
|
expect(body.manifest.collaborators).toEqual([...SAVE_AGENT_COLLABORATORS]);
|
|
609
650
|
});
|
|
651
|
+
|
|
652
|
+
it("extracts agentId and versionId from a wrapped CP version response", async () => {
|
|
653
|
+
vi.stubGlobal(
|
|
654
|
+
"fetch",
|
|
655
|
+
vi.fn(async () =>
|
|
656
|
+
Response.json({
|
|
657
|
+
data: { id: "ver_9", agentId: "ag_1", name: "my-agent" },
|
|
658
|
+
}),
|
|
659
|
+
),
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
const result = await saveAgent(
|
|
663
|
+
{ apiKey: "key", cpURL: "https://cp.example/" },
|
|
664
|
+
{
|
|
665
|
+
agentName: "my-agent",
|
|
666
|
+
agentSpec: { model: { name: "openai-main/gpt-4.1" } },
|
|
667
|
+
intent: "update",
|
|
668
|
+
},
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
expect(result).toEqual({ agentId: "ag_1", versionId: "ver_9" });
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
describe("saveAgentResultFromCp", () => {
|
|
676
|
+
it("reads { data: { id, name } } (no agentId) as the agent itself", () => {
|
|
677
|
+
expect(
|
|
678
|
+
saveAgentResultFromCp({ data: { id: "ag_1", name: "my-agent" } }),
|
|
679
|
+
).toEqual({ agentId: "ag_1" });
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it("reads { data: { id, agentId } } as version + agent", () => {
|
|
683
|
+
expect(
|
|
684
|
+
saveAgentResultFromCp({ data: { id: "ver_1", agentId: "ag_2" } }),
|
|
685
|
+
).toEqual({ agentId: "ag_2", versionId: "ver_1" });
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("returns {} for malformed payloads", () => {
|
|
689
|
+
expect(saveAgentResultFromCp(null)).toEqual({});
|
|
690
|
+
expect(saveAgentResultFromCp({ id: "not-wrapped" })).toEqual({});
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
describe("old saved-agent manifest round-trip", () => {
|
|
695
|
+
const oldManifest = {
|
|
696
|
+
type: "truefoundry-agent",
|
|
697
|
+
name: "ask-ai-agent",
|
|
698
|
+
description: "Answer questions",
|
|
699
|
+
model: {
|
|
700
|
+
name: "openai-main/gpt-4.1",
|
|
701
|
+
params: { max_tokens: 8192, reasoning_effort: "medium" },
|
|
702
|
+
},
|
|
703
|
+
instructions: "Be helpful",
|
|
704
|
+
// Variable names are user keys — `my_city` must never become `myCity`.
|
|
705
|
+
variables: {
|
|
706
|
+
city: "Berlin",
|
|
707
|
+
my_city: { default_value: "Pune", description: "home town" },
|
|
708
|
+
},
|
|
709
|
+
messages: [{ role: "user", content: "Hello {{city}}" }],
|
|
710
|
+
// Uppercase tag keys are the regression case: snake-casing them once
|
|
711
|
+
// produced "_t_f_y__a_l_p_h_a__…" on the wire.
|
|
712
|
+
tags: {
|
|
713
|
+
env: "prod",
|
|
714
|
+
owner: "platform",
|
|
715
|
+
TFY_ALPHA_ENABLE_OPENUI: "true",
|
|
716
|
+
},
|
|
717
|
+
collaborators: [
|
|
718
|
+
{ subject: "team:everyone", role_id: "agent-access" },
|
|
719
|
+
],
|
|
720
|
+
response_format: {
|
|
721
|
+
type: "json_schema",
|
|
722
|
+
json_schema: {
|
|
723
|
+
name: "answer",
|
|
724
|
+
// Schema property names are user data — `user_name` must
|
|
725
|
+
// survive both directions verbatim.
|
|
726
|
+
schema: {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: { user_name: { type: "string" } },
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
},
|
|
732
|
+
config: {
|
|
733
|
+
iteration_limit: 50,
|
|
734
|
+
ask_user_questions: { enabled: true },
|
|
735
|
+
sandbox: { enabled: true, file_downloads: true },
|
|
736
|
+
},
|
|
737
|
+
mcp_servers: [
|
|
738
|
+
{
|
|
739
|
+
type: "truefoundry-mcp-registry",
|
|
740
|
+
name: "gmail",
|
|
741
|
+
enable_tools: ["@read-only"],
|
|
742
|
+
preload: true,
|
|
743
|
+
},
|
|
744
|
+
],
|
|
745
|
+
skills: [
|
|
746
|
+
{
|
|
747
|
+
type: "truefoundry-skills-registry",
|
|
748
|
+
fqn: "agent-skill:tfy/skills/web:1",
|
|
749
|
+
preload: false,
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
it("reads snake_case fields into camelCase spec and writes them back", () => {
|
|
755
|
+
const spec = agentSpecFromCpManifest(oldManifest);
|
|
756
|
+
expect(spec?.description).toBe("Answer questions");
|
|
757
|
+
// User keys verbatim; `{ default_value }` records collapse to strings.
|
|
758
|
+
expect(spec?.variables).toEqual({ city: "Berlin", my_city: "Pune" });
|
|
759
|
+
expect(spec?.messages).toEqual([
|
|
760
|
+
{ role: "user", content: "Hello {{city}}" },
|
|
761
|
+
]);
|
|
762
|
+
expect(spec?.tags).toEqual({
|
|
763
|
+
env: "prod",
|
|
764
|
+
owner: "platform",
|
|
765
|
+
TFY_ALPHA_ENABLE_OPENUI: "true",
|
|
766
|
+
});
|
|
767
|
+
expect(spec?.collaborators).toEqual([
|
|
768
|
+
{ subject: "team:everyone", roleId: "agent-access" },
|
|
769
|
+
]);
|
|
770
|
+
expect(spec?.responseFormat).toEqual({
|
|
771
|
+
type: "json_schema",
|
|
772
|
+
jsonSchema: {
|
|
773
|
+
name: "answer",
|
|
774
|
+
schema: {
|
|
775
|
+
type: "object",
|
|
776
|
+
properties: { user_name: { type: "string" } },
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
});
|
|
780
|
+
expect(spec?.model.params).toEqual({
|
|
781
|
+
maxTokens: 8192,
|
|
782
|
+
reasoningEffort: "medium",
|
|
783
|
+
});
|
|
784
|
+
expect(spec?.config).toEqual({
|
|
785
|
+
iterationLimit: 50,
|
|
786
|
+
askUserQuestions: { enabled: true },
|
|
787
|
+
sandbox: { enabled: true, fileDownloads: true },
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const saved = buildSaveAgentManifest("ask-ai-agent", spec!);
|
|
791
|
+
expect(saved.description).toBe("Answer questions");
|
|
792
|
+
expect(saved.variables).toEqual({ city: "Berlin", my_city: "Pune" });
|
|
793
|
+
expect(saved.messages).toEqual([
|
|
794
|
+
{ role: "user", content: "Hello {{city}}" },
|
|
795
|
+
]);
|
|
796
|
+
expect(saved.tags).toEqual({
|
|
797
|
+
env: "prod",
|
|
798
|
+
owner: "platform",
|
|
799
|
+
TFY_ALPHA_ENABLE_OPENUI: "true",
|
|
800
|
+
});
|
|
801
|
+
expect(saved.collaborators).toEqual([
|
|
802
|
+
{ subject: "team:everyone", role_id: "agent-access" },
|
|
803
|
+
]);
|
|
804
|
+
expect(saved.response_format).toEqual({
|
|
805
|
+
type: "json_schema",
|
|
806
|
+
json_schema: {
|
|
807
|
+
name: "answer",
|
|
808
|
+
schema: {
|
|
809
|
+
type: "object",
|
|
810
|
+
properties: { user_name: { type: "string" } },
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
expect(saved.model).toEqual({
|
|
815
|
+
name: "openai-main/gpt-4.1",
|
|
816
|
+
params: { max_tokens: 8192, reasoning_effort: "medium" },
|
|
817
|
+
});
|
|
818
|
+
expect(saved.config).toEqual({
|
|
819
|
+
iteration_limit: 50,
|
|
820
|
+
ask_user_questions: { enabled: true },
|
|
821
|
+
sandbox: { enabled: true, file_downloads: true },
|
|
822
|
+
});
|
|
823
|
+
expect(saved.mcp_servers).toEqual([
|
|
824
|
+
{
|
|
825
|
+
type: "truefoundry-mcp-registry",
|
|
826
|
+
name: "gmail",
|
|
827
|
+
enable_tools: ["@read-only"],
|
|
828
|
+
preload: true,
|
|
829
|
+
},
|
|
830
|
+
]);
|
|
831
|
+
expect(saved.skills).toEqual([
|
|
832
|
+
{
|
|
833
|
+
type: "truefoundry-skills-registry",
|
|
834
|
+
fqn: "agent-skill:tfy/skills/web:1",
|
|
835
|
+
preload: false,
|
|
836
|
+
},
|
|
837
|
+
]);
|
|
838
|
+
});
|
|
610
839
|
});
|