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>>(), |
# Conflicts: # packages/opencode/src/session/projectors.ts # packages/opencode/src/session/session.sql.ts # packages/opencode/src/session/session.ts # packages/opencode/test/server/session-actions.test.ts # packages/opencode/test/server/session-list.test.ts # packages/opencode/test/session/session.test.ts
|
Addressed the latest session metadata review feedback in 2c9c3ac. Changes made:
|
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
|
/review |
| path: sessionPath(ctx.worktree, ctx.directory), | ||
| workspaceID: original.workspaceID, | ||
| title, | ||
| metadata: structuredClone(original.metadata), |
There was a problem hiding this comment.
Suggestion: if copyMetadata is still intended for this feature, this always-copy behavior leaves no API/SDK path to opt out. The final fork schema only accepts messageID, and the generated SDK no longer includes copyMetadata, so clients cannot request the documented non-copying fork behavior. If that behavior was intentionally dropped, updating the PR/API expectation would avoid the mismatch.
|
@rekram1-node - Apologies for chasing but can this please be merged in? |
Match the optional treatment of every comparable schema field (Session.Info.permission, CreateInput, message-v2 parts). metadata is now optionalOmitUndefined and omitted when unset/cleared rather than materialized as {}. Lets unrelated session fixtures drop their forced metadata: {} entries.
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.