Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/20260923112635-regenerate-sdk-from-openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@truefoundry/trueforge-sdk": patch
---

Regenerate SDK from updated OpenAPI spec.
6 changes: 6 additions & 0 deletions .changeset/session-shared-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@truefoundry/trueforge': patch
'@truefoundry/trueforge-core': patch
---

Let a session owner mark a session shared so any subject in the tenant can read it by id, including turns and events (not subscribe or sandbox downloads).
31 changes: 20 additions & 11 deletions .github/fern/openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -4028,6 +4028,10 @@
"metrics": {
"$ref": "#/components/schemas/SessionMetrics"
},
"shared": {
"description": "When true, any subject in the tenant may read this session and its turns/events by id.",
"type": "boolean"
},
"source": {
"$ref": "#/components/schemas/SessionSource"
},
Expand All @@ -4047,6 +4051,7 @@
"id",
"agent",
"title",
"shared",
"created_by_subject",
"created_at",
"updated_at",
Expand Down Expand Up @@ -5832,6 +5837,10 @@
"metadata": {
"$ref": "#/components/schemas/SessionMetadata"
},
"shared": {
"description": "When true, any subject in the tenant may read this session and its turns/events by id.",
"type": "boolean"
},
"title": {
"description": "Human-readable session title.",
"maxLength": 50,
Expand Down Expand Up @@ -8802,7 +8811,7 @@
"x-fern-sdk-method-name": "delete"
},
"get": {
"description": "Fetch a session by ID. Only the session creator may fetch it.",
"description": "Fetch a session by ID. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -8836,7 +8845,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand All @@ -8859,7 +8868,7 @@
"x-fern-sdk-method-name": "get"
},
"patch": {
"description": "Update a session: optional `title`, `metadata`, and (inline sessions only) `agent` as `{ spec: AgentSpec }`. Named sessions reject agent updates. An empty body is a valid no-op that refreshes `updated_at`. Only the session creator may update it.",
"description": "Update a session: optional `title`, `metadata`, `shared`, and (inline sessions only) `agent` as `{ spec: AgentSpec }`. Named sessions reject agent updates. An empty body is a valid no-op that refreshes `updated_at`. Only the session creator may update it.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9027,7 +9036,7 @@
},
"/api/v1/sessions/{session_id}/events": {
"get": {
"description": "List session events as `{ turn_id, event }` across the active turn branch (newest first), including persisted events from a running tip. Each turn contributes turn.created, content events (model.message, tool.call, …), and turn.done when terminal; streaming deltas are not included. Use `page_token` to paginate backward toward older events while retaining the original branch anchor. Only the session creator may list events.",
"description": "List session events as `{ turn_id, event }` across the active turn branch (newest first), including persisted events from a running tip. Each turn contributes turn.created, content events (model.message, tool.call, …), and turn.done when terminal; streaming deltas are not included. Use `page_token` to paginate backward toward older events while retaining the original branch anchor. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9104,7 +9113,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down Expand Up @@ -9134,7 +9143,7 @@
},
"/api/v1/sessions/{session_id}/turns": {
"get": {
"description": "List turns for a session (newest first by default), token-paginated. Only the session creator may list turns.",
"description": "List turns for a session (newest first by default), token-paginated. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9201,7 +9210,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down Expand Up @@ -9364,7 +9373,7 @@
},
"/api/v1/sessions/{session_id}/turns/{turn_id}": {
"get": {
"description": "Fetch a single turn by ID. Only the session creator may fetch it.",
"description": "Fetch a single turn by ID. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9409,7 +9418,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down Expand Up @@ -9566,7 +9575,7 @@
},
"/api/v1/sessions/{session_id}/turns/{turn_id}/events": {
"get": {
"description": "Paginated persisted events for a turn (insertion order by default). Only the session creator may list events.",
"description": "Paginated persisted events for a turn (insertion order by default). Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9653,7 +9662,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down
31 changes: 20 additions & 11 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -4028,6 +4028,10 @@
"metrics": {
"$ref": "#/components/schemas/SessionMetrics"
},
"shared": {
"description": "When true, any subject in the tenant may read this session and its turns/events by id.",
"type": "boolean"
},
"source": {
"$ref": "#/components/schemas/SessionSource"
},
Expand All @@ -4047,6 +4051,7 @@
"id",
"agent",
"title",
"shared",
"created_by_subject",
"created_at",
"updated_at",
Expand Down Expand Up @@ -5832,6 +5837,10 @@
"metadata": {
"$ref": "#/components/schemas/SessionMetadata"
},
"shared": {
"description": "When true, any subject in the tenant may read this session and its turns/events by id.",
"type": "boolean"
},
"title": {
"description": "Human-readable session title.",
"maxLength": 50,
Expand Down Expand Up @@ -8802,7 +8811,7 @@
"x-fern-sdk-method-name": "delete"
},
"get": {
"description": "Fetch a session by ID. Only the session creator may fetch it.",
"description": "Fetch a session by ID. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -8836,7 +8845,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand All @@ -8859,7 +8868,7 @@
"x-fern-sdk-method-name": "get"
},
"patch": {
"description": "Update a session: optional `title`, `metadata`, and (inline sessions only) `agent` as `{ spec: AgentSpec }`. Named sessions reject agent updates. An empty body is a valid no-op that refreshes `updated_at`. Only the session creator may update it.",
"description": "Update a session: optional `title`, `metadata`, `shared`, and (inline sessions only) `agent` as `{ spec: AgentSpec }`. Named sessions reject agent updates. An empty body is a valid no-op that refreshes `updated_at`. Only the session creator may update it.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9027,7 +9036,7 @@
},
"/api/v1/sessions/{session_id}/events": {
"get": {
"description": "List session events as `{ turn_id, event }` across the active turn branch (newest first), including persisted events from a running tip. Each turn contributes turn.created, content events (model.message, tool.call, …), and turn.done when terminal; streaming deltas are not included. Use `page_token` to paginate backward toward older events while retaining the original branch anchor. Only the session creator may list events.",
"description": "List session events as `{ turn_id, event }` across the active turn branch (newest first), including persisted events from a running tip. Each turn contributes turn.created, content events (model.message, tool.call, …), and turn.done when terminal; streaming deltas are not included. Use `page_token` to paginate backward toward older events while retaining the original branch anchor. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9104,7 +9113,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down Expand Up @@ -9134,7 +9143,7 @@
},
"/api/v1/sessions/{session_id}/turns": {
"get": {
"description": "List turns for a session (newest first by default), token-paginated. Only the session creator may list turns.",
"description": "List turns for a session (newest first by default), token-paginated. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9201,7 +9210,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down Expand Up @@ -9364,7 +9373,7 @@
},
"/api/v1/sessions/{session_id}/turns/{turn_id}": {
"get": {
"description": "Fetch a single turn by ID. Only the session creator may fetch it.",
"description": "Fetch a single turn by ID. Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9409,7 +9418,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down Expand Up @@ -9566,7 +9575,7 @@
},
"/api/v1/sessions/{session_id}/turns/{turn_id}/events": {
"get": {
"description": "Paginated persisted events for a turn (insertion order by default). Only the session creator may list events.",
"description": "Paginated persisted events for a turn (insertion order by default). Allowed for the creator, a manager of the bound named agent, or any tenant member when the session is shared.",
"parameters": [
{
"description": "Session identifier.",
Expand Down Expand Up @@ -9653,7 +9662,7 @@
}
}
},
"description": "Caller is not the session creator."
"description": "Caller cannot read this session."
},
"404": {
"content": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface SessionRecord<TCustom extends object = Record<string, never>> {
* update_session_title_if_not_exist (first write wins; caller derives).
*/
title: string | null;
/**
* When true, any subject in the tenant may GET this session.
* Mutations stay owner-only. List filtering ignores this flag.
*/
shared: boolean;
/**
* Optional caller-supplied key, unique within a tenant when set.
* Null means the session has no external id.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export const SessionSchema = z
id: z.string().describe('Unique session id.'),
agent: SessionAgentSchema,
title: z.string().nullable().describe('Optional human-readable title; null until set.'),
shared: z
Comment thread
chiragjn marked this conversation as resolved.
.boolean()
.describe('When true, any subject in the tenant may read this session and its turns/events by id.'),
created_by_subject: CreatedBySubjectSchema,
created_at: z.string().describe('ISO 8601 creation timestamp.'),
updated_at: z.string().describe('ISO 8601 last-update timestamp.'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export type UpdateSessionInput<TSessionCustom extends object = Record<string, ne
agent: Extract<SessionRecord<TSessionCustom>['agent'], { type: 'inline' }> | undefined;
title: SessionRecord<TSessionCustom>['title'] | undefined;
metadata: SessionRecord<TSessionCustom>['metadata'] | undefined;
/** When omitted, the stored flag is left unchanged. */
shared: SessionRecord<TSessionCustom>['shared'] | undefined;
};

export interface GetSessionInput {
Expand Down Expand Up @@ -293,6 +295,7 @@ export interface ISessionStore<
* - agent: replace inline binding (inline sessions only; reference → invariant error).
* - title: set/replace the session title.
* - metadata: full replace of the caller-owned string map when set.
* - shared: set/replace the share flag when set; omitted leaves the stored value.
* Bumps `last_activity_timestamp_ms` (= now) in the same update.
*/
updateSession(input: UpdateSessionInput<TSessionCustom>): Promise<void>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export class InMemorySessionStore<
created_by_subject: input.created_by_subject,
agent: deepCopy(input.agent),
title: null,
shared: false,
last_turn_id: null,
external_id: externalId,
source: input.source !== null ? deepCopy(input.source) : null,
Expand Down Expand Up @@ -285,6 +286,9 @@ export class InMemorySessionStore<
if (input.metadata !== undefined) {
stored.record.metadata = deepCopy(input.metadata);
}
if (input.shared !== undefined) {
stored.record.shared = input.shared;
}
const now = Date.now();
stored.record.updated_at = new Date(now);
stored.record.last_activity_timestamp_ms = now;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('Sessions / SessionHandle / TurnHandle (storage + createTurn)', () => {
agent: undefined,
title: undefined,
metadata: { env: 'prod' },
shared: undefined,
});
const afterReplace = await sessions.get({ tenant_id: tenant, session_id: 's-meta' });
expect(afterReplace?.metadata).toEqual({ env: 'prod' });
Expand All @@ -38,6 +39,7 @@ describe('Sessions / SessionHandle / TurnHandle (storage + createTurn)', () => {
agent: undefined,
title: 't',
metadata: undefined,
shared: undefined,
});
const afterOmit = await sessions.get({ tenant_id: tenant, session_id: 's-meta' });
expect(afterOmit?.record.title).toBe('t');
Expand Down
Loading
Loading