feat(core): add session metadata support#23068
Conversation
Persist session metadata in the session store and expose it across the session APIs and SDK so clients can round-trip metadata during create, get, update, list, and fork operations.
Add a JSON metadata column to the session table, thread metadata through the session model/projectors, and normalize empty metadata responses to {}.
Support full metadata replacement on session update, add copyMetadata to session forks with default copy behavior, regenerate the session SDK surface, and cover the behavior with server/session tests plus a migration for existing databases.
|
/review |
| @@ -33,6 +33,7 @@ export const SessionTable = sqliteTable( | |||
| summary_deletions: integer(), | |||
| summary_files: integer(), | |||
| summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(), | |||
There was a problem hiding this comment.
Suggestion (style): this new metadata type uses any, which the repo style guide asks us to avoid. Since this is client-controlled JSON and the generated SDK exposes unknown, consider carrying Record<string, unknown> through the storage/service types instead of Record<string, any>.
| export type SessionUpdateData = { | ||
| body?: { | ||
| title?: string | ||
| metadata?: { |
There was a problem hiding this comment.
Suggestion: the update route accepts metadata: null to clear metadata, but the generated SDK type only allows an object here. That means SDK callers cannot type-safely use the clear path covered by the API/tests. Since this file is generated, the source fix may need to make the nullable schema survive generation so this becomes { [key: string]: unknown } | null.
# Conflicts: # packages/opencode/src/session/session.ts
|
/review |
| const Metadata = Schema.Record(Schema.String, Schema.Any) | ||
|
|
||
| export const Info = Schema.Struct({ | ||
| id: SessionID, |
There was a problem hiding this comment.
Suggestion for the human to consider: this new field introduces any, which is avoidable here and the PR describes this as JSON metadata. Using a JSON schema keeps service-level callers from passing non-serializable values that HTTP clients could never send.
| id: SessionID, | |
| const Metadata = Schema.Record(Schema.String, Schema.Json) |
| export type SessionUpdateData = { | ||
| body?: { | ||
| title?: string | ||
| metadata?: { |
There was a problem hiding this comment.
Suggestion for the human to consider: the server schema and new route test both allow PATCH /session/:id with metadata: null to clear metadata, but the generated SDK update payload does not include null. If clearing with null is intended to be public SDK behavior, this generated type should include | null after the source/schema change and SDK regeneration.
| summary_deletions: integer(), | ||
| summary_files: integer(), | ||
| summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(), | ||
| metadata: text({ mode: "json" }).$type<Record<string, any>>(), |
There was a problem hiding this comment.
Suggestion for the human to consider: this new DB type introduces any, which the style guide asks us to avoid. If the runtime schema is narrowed to JSON, this can also avoid widening the persisted shape to any here.
| metadata: text({ mode: "json" }).$type<Record<string, any>>(), | |
| metadata: text({ mode: "json" }).$type<Record<string, unknown>>(), |
Issue for this PR
Closes #
Type of change
What does this PR do?
Introduces a metadata field in sessions that can be used with SDK to persist client controlled JSON metadata for session. This is simlar to metadata available in Message.Parts
Session APIs and SDK exposes metadata for create, get, update, list, and fork operations.
Support full metadata replacement on session update, add copyMetadata to session forks with default copy behavior,
If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!
How did you verify your code works?
Added tests and manual testing.
Screenshots / recordings
If this is a UI change, please include a screenshot or recording.
Checklist
If you do not follow this template your PR will be automatically rejected.