Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
707 changes: 410 additions & 297 deletions docs/openapi.json

Large diffs are not rendered by default.

19 changes: 12 additions & 7 deletions packages/frontend/src/harnessBuilderServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,21 @@ export function createHarnessBuilderServer(
return filtered.slice(offset, offset + limit).map(toLibraryEntry);
},

async saveAgent({ agentName, agentSpec, intent }) {
async saveAgent({ agentName, agentSpec, intent, sessionId }) {
const manifest = toHarnessAgentSpec(agentSpec);
const request = sessionId === undefined ? manifest : { ...manifest, sessionId };
if (intent === 'update') {
const { data } = await client.agents.list();
const existing = data.find(agent => agent.name === agentName);
await client.agents.update(agentName, manifest);
return existing === undefined ? {} : { agentId: existing.id };
const updated = await client.agents.update(agentName, request);
return {
agentId: updated.data.id,
...(updated.sessionUpdatedAt === undefined ? {} : { sessionUpdatedAt: updated.sessionUpdatedAt }),
};
}
const created = await client.agents.create({ name: agentName, ...manifest });
return { agentId: created.data.id };
const created = await client.agents.create({ name: agentName, ...request });
return {
agentId: created.data.id,
...(created.sessionUpdatedAt === undefined ? {} : { sessionUpdatedAt: created.sessionUpdatedAt }),
};
},
};
}
26 changes: 17 additions & 9 deletions packages/frontend/tests/harnessBuilderServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ describe('harnessBuilderServer', () => {
);
});

it('saveAgent creates when the name is new', async () => {
it('saveAgent creates directly from explicit intent', async () => {
const requests: { method: string; url: string; body?: unknown }[] = [];
const fetchMock: typeof fetch = async (input, init) => {
const url = input instanceof Request ? input.url : String(input);
Expand All @@ -219,6 +219,7 @@ describe('harnessBuilderServer', () => {
requests.push({ method, url, body: JSON.parse(init.body) });
return Response.json({
data: { id: 'agt_new', name: 'saved-agent', model: { name: 'test/model' } },
session_updated_at: '2026-08-12T08:00:00.000Z',
});
}
return new Response(`Unexpected request: ${method} ${url}`, { status: 500 });
Expand All @@ -232,26 +233,27 @@ describe('harnessBuilderServer', () => {
skills: [{ name: 'review' }],
},
intent: 'create',
sessionId: 'draft-1',
});

assert.deepEqual(result, { agentId: 'agt_new' });
assert.deepEqual(result, {
agentId: 'agt_new',
sessionUpdatedAt: '2026-08-12T08:00:00.000Z',
});
assert.equal(requests.length, 1);
assert.deepEqual(requests.at(-1)?.body, {
name: 'saved-agent',
model: { name: 'test/model' },
skills: [{ name: 'review' }],
session_id: 'draft-1',
});
});

it('saveAgent updates when the name already exists', async () => {
it('saveAgent updates directly from explicit intent', async () => {
const requests: { method: string; url: string; body?: unknown }[] = [];
const fetchMock: typeof fetch = async (input, init) => {
const url = input instanceof Request ? input.url : String(input);
const method = init?.method ?? 'GET';
if (url.endsWith('/api/v1/agents') && method === 'GET') {
return Response.json({
data: [{ id: 'agt_1', name: 'writer', model: { name: 'test/model' } }],
});
}
if (url.endsWith('/api/v1/agents/writer') && method === 'PUT' && typeof init?.body === 'string') {
requests.push({ method, url, body: JSON.parse(init.body) });
return Response.json({
Expand All @@ -261,6 +263,7 @@ describe('harnessBuilderServer', () => {
model: { name: 'test/model' },
instructions: 'Write release notes.',
},
session_updated_at: '2026-08-12T09:00:00.000Z',
});
}
return new Response(`Unexpected request: ${method} ${url}`, { status: 500 });
Expand All @@ -274,13 +277,18 @@ describe('harnessBuilderServer', () => {
instructions: 'Write release notes.',
},
intent: 'update',
sessionId: 'draft-2',
});

assert.deepEqual(result, { agentId: 'agt_1' });
assert.deepEqual(result, {
agentId: 'agt_1',
sessionUpdatedAt: '2026-08-12T09:00:00.000Z',
});
assert.equal(requests.length, 1);
assert.deepEqual(requests[0]?.body, {
model: { name: 'test/model' },
instructions: 'Write release notes.',
session_id: 'draft-2',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ import type * as TrueForge from "../../../../index.js";
export interface AgentWriteRequest extends TrueForge.AgentSpec {
/** Fully qualified name. Unique within a tenant. */
name: string;
/** Mutable inline session updated atomically with this agent. */
sessionId?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface UpdateAgentRequest {
messages?: TrueForge.AgentSpecUserMessage[];
model: TrueForge.AgentSpecModel;
responseFormat?: TrueForge.ResponseFormat;
/** Mutable inline session updated atomically with this agent. */
sessionId?: string;
/** Optional name-only skill references. Requires `config.sandbox.enabled: true`. */
skills?: TrueForge.SkillNameRef[];
}
2 changes: 2 additions & 0 deletions packages/sdk/src/api/types/CreateAgentResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import type * as TrueForge from "../index.js";

export interface CreateAgentResponse {
data: TrueForge.Agent;
/** Updated session timestamp when `sessionId` was supplied. */
sessionUpdatedAt?: string;
}
2 changes: 2 additions & 0 deletions packages/sdk/src/api/types/PutAgentResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import type * as TrueForge from "../index.js";

export interface PutAgentResponse {
data: TrueForge.Agent;
/** Updated session timestamp when `sessionId` was supplied. */
sessionUpdatedAt?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ export const AgentWriteRequest: core.serialization.Schema<
> = core.serialization
.object({
name: core.serialization.string(),
sessionId: core.serialization.property("session_id", core.serialization.string().optional()),
})
.extend(AgentSpec);

export declare namespace AgentWriteRequest {
export interface Raw extends AgentSpec.Raw {
name: string;
session_id?: string | null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const UpdateAgentRequest: core.serialization.Schema<
messages: core.serialization.list(AgentSpecUserMessage).optional(),
model: AgentSpecModel,
responseFormat: core.serialization.property("response_format", ResponseFormat.optional()),
sessionId: core.serialization.property("session_id", core.serialization.string().optional()),
skills: core.serialization.list(SkillNameRef).optional(),
});

Expand All @@ -31,6 +32,7 @@ export declare namespace UpdateAgentRequest {
messages?: AgentSpecUserMessage.Raw[] | null;
model: AgentSpecModel.Raw;
response_format?: ResponseFormat.Raw | null;
session_id?: string | null;
skills?: SkillNameRef.Raw[] | null;
}
}
2 changes: 2 additions & 0 deletions packages/sdk/src/serialization/types/CreateAgentResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ export const CreateAgentResponse: core.serialization.ObjectSchema<
TrueForge.CreateAgentResponse
> = core.serialization.object({
data: Agent,
sessionUpdatedAt: core.serialization.property("session_updated_at", core.serialization.string().optional()),
});

export declare namespace CreateAgentResponse {
export interface Raw {
data: Agent.Raw;
session_updated_at?: string | null;
}
}
2 changes: 2 additions & 0 deletions packages/sdk/src/serialization/types/PutAgentResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ export const PutAgentResponse: core.serialization.ObjectSchema<
TrueForge.PutAgentResponse
> = core.serialization.object({
data: Agent,
sessionUpdatedAt: core.serialization.property("session_updated_at", core.serialization.string().optional()),
});

export declare namespace PutAgentResponse {
export interface Raw {
data: Agent.Raw;
session_updated_at?: string | null;
}
}
10 changes: 8 additions & 2 deletions packages/sdk/tests/wire/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe("AgentsClient", () => {
test("create (1)", async () => {
const server = mockServerPool.createServer();
const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
const rawRequestBody = { model: { name: "name" }, name: "name" };
const rawRequestBody = { model: { name: "name" }, name: "name", session_id: "session_id" };
const rawResponseBody = {
data: {
config: { iteration_limit: 1, sandbox: { enabled: true } },
Expand All @@ -89,6 +89,7 @@ describe("AgentsClient", () => {
id: "id",
name: "name",
},
session_updated_at: "2026-08-12T08:00:00.000Z",
};

server
Expand All @@ -105,6 +106,7 @@ describe("AgentsClient", () => {
name: "name",
},
name: "name",
sessionId: "session_id",
});
expect(response).toEqual({
data: {
Expand Down Expand Up @@ -140,6 +142,7 @@ describe("AgentsClient", () => {
id: "id",
name: "name",
},
sessionUpdatedAt: "2026-08-12T08:00:00.000Z",
});
});

Expand Down Expand Up @@ -333,7 +336,7 @@ describe("AgentsClient", () => {
test("update (1)", async () => {
const server = mockServerPool.createServer();
const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
const rawRequestBody = { model: { name: "name" } };
const rawRequestBody = { model: { name: "name" }, session_id: "session_id" };
const rawResponseBody = {
data: {
config: { iteration_limit: 1, sandbox: { enabled: true } },
Expand All @@ -346,6 +349,7 @@ describe("AgentsClient", () => {
id: "id",
name: "name",
},
session_updated_at: "2026-08-12T09:00:00.000Z",
};

server
Expand All @@ -361,6 +365,7 @@ describe("AgentsClient", () => {
model: {
name: "name",
},
sessionId: "session_id",
});
expect(response).toEqual({
data: {
Expand Down Expand Up @@ -396,6 +401,7 @@ describe("AgentsClient", () => {
id: "id",
name: "name",
},
sessionUpdatedAt: "2026-08-12T09:00:00.000Z",
});
});

Expand Down
13 changes: 9 additions & 4 deletions packages/trueforge-ui-sdk/example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1425,10 +1425,10 @@
"@tailwindcss/oxide" "4.3.3"
tailwindcss "4.3.3"

"@truefoundry/assistant-ui-runtime@0.1.13":
version "0.1.13"
resolved "https://registry.yarnpkg.com/@truefoundry/assistant-ui-runtime/-/assistant-ui-runtime-0.1.13.tgz#64191bf904a31c5a32f45c3314ebe86117f2e57a"
integrity sha512-pmY4Fvm5tZmX+uhsr3RegTeY3yYrdQdqaqYV5QzPZyrOPloboVPO07jbcZ2/D7S33622Ug0drhKGvWHpYTMTDw==
"@truefoundry/assistant-ui-runtime@0.1.14":
version "0.1.14"
resolved "https://registry.yarnpkg.com/@truefoundry/assistant-ui-runtime/-/assistant-ui-runtime-0.1.14.tgz#c77b2f41ae260b965c16d72dc1b9f73ddde66813"
integrity sha512-xdFob3v6KdhUVFrDuw1OAcX6o8lsnT+kQAo82gzCgXbg3YvdIs4cDWRswZdCI83FriRieYtMc8kZPjrRysJSkQ==
dependencies:
"@assistant-ui/core" "^0.2.22"
"@assistant-ui/store" "^0.2.21"
Expand Down Expand Up @@ -2859,6 +2859,11 @@ parse5@^7.0.0:
dependencies:
entities "^6.0.0"

partial-json@^0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/partial-json/-/partial-json-0.1.7.tgz#b735a89edb3e25f231a3c4caeaae71dc9f578605"
integrity sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==

picocolors@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
Expand Down
Loading
Loading