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
4 changes: 3 additions & 1 deletion cli/src/agent/sessionFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type SessionBootstrapOptions = {
agentState?: AgentState | null
model?: string
effort?: string
modelReasoningEffort?: string
metadataOverrides?: Partial<Metadata>
}

Expand Down Expand Up @@ -133,7 +134,8 @@ export async function bootstrapSession(options: SessionBootstrapOptions): Promis
metadata,
state: agentState,
model: options.model,
effort: options.effort
effort: options.effort,
modelReasoningEffort: options.modelReasoningEffort
})

const session = api.sessionSyncClient(sessionInfo)
Expand Down
4 changes: 3 additions & 1 deletion cli/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class ApiClient {
state: AgentState | null
model?: string
effort?: string
modelReasoningEffort?: string
}): Promise<Session> {
const response = await axios.post<CreateSessionResponse>(
`${configuration.apiUrl}/cli/sessions`,
Expand All @@ -28,7 +29,8 @@ export class ApiClient {
metadata: opts.metadata,
agentState: opts.state,
model: opts.model,
effort: opts.effort
effort: opts.effort,
modelReasoningEffort: opts.modelReasoningEffort
},
{
headers: {
Expand Down
23 changes: 19 additions & 4 deletions cli/src/codex/runCodex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export async function runCodex(opts: {
startedBy,
workingDirectory,
agentState: state,
model: opts.model
model: opts.model,
modelReasoningEffort: opts.modelReasoningEffort
});

const startingMode: 'local' | 'remote' = startedBy === 'runner' ? 'remote' : 'local';
Expand All @@ -56,7 +57,7 @@ export async function runCodex(opts: {

let currentPermissionMode: PermissionMode = opts.permissionMode ?? 'default';
let currentModel = opts.model;
const currentModelReasoningEffort = opts.modelReasoningEffort;
let currentModelReasoningEffort = opts.modelReasoningEffort;
let currentCollaborationMode: EnhancedMode['collaborationMode'] = 'default';

const lifecycle = createRunnerLifecycle({
Expand Down Expand Up @@ -145,11 +146,21 @@ export async function runCodex(opts: {
return parsed.data;
};

const resolveModelReasoningEffort = (value: unknown): ReasoningEffort | undefined => {
if (value === null || value === undefined) {
return undefined;
}
if (typeof value !== 'string') {
throw new Error('Invalid model reasoning effort');
}
return value as ReasoningEffort;
};

session.rpcHandlerManager.registerHandler('set-session-config', async (payload: unknown) => {
if (!payload || typeof payload !== 'object') {
throw new Error('Invalid session config payload');
}
const config = payload as { permissionMode?: unknown; collaborationMode?: unknown };
const config = payload as { permissionMode?: unknown; collaborationMode?: unknown; modelReasoningEffort?: unknown };

if (config.permissionMode !== undefined) {
currentPermissionMode = resolvePermissionMode(config.permissionMode);
Expand All @@ -159,8 +170,12 @@ export async function runCodex(opts: {
currentCollaborationMode = resolveCollaborationMode(config.collaborationMode);
}

if ('modelReasoningEffort' in config) {
currentModelReasoningEffort = resolveModelReasoningEffort(config.modelReasoningEffort);
}

syncSessionMode();
return { applied: { permissionMode: currentPermissionMode, collaborationMode: currentCollaborationMode } };
return { applied: { permissionMode: currentPermissionMode, collaborationMode: currentCollaborationMode, modelReasoningEffort: currentModelReasoningEffort ?? null } };
});

try {
Expand Down
26 changes: 24 additions & 2 deletions hub/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { PushStore } from './pushStore'
export { SessionStore } from './sessionStore'
export { UserStore } from './userStore'

const SCHEMA_VERSION: number = 6
const SCHEMA_VERSION: number = 7
const REQUIRED_TABLES = [
'sessions',
'machines',
Expand Down Expand Up @@ -128,9 +128,23 @@ export class Store {
return
}

if (currentVersion === 4 && SCHEMA_VERSION === 6) {
if (currentVersion === 5 && SCHEMA_VERSION === 7) {
this.migrateFromV5ToV6()
this.migrateFromV6ToV7()
this.setUserVersion(SCHEMA_VERSION)
return
}

if (currentVersion === 6 && SCHEMA_VERSION === 7) {
this.migrateFromV6ToV7()
this.setUserVersion(SCHEMA_VERSION)
return
}

if (currentVersion === 4 && SCHEMA_VERSION === 7) {
this.migrateFromV4ToV5()
this.migrateFromV5ToV6()
this.migrateFromV6ToV7()
this.setUserVersion(SCHEMA_VERSION)
return
}
Expand All @@ -157,6 +171,7 @@ export class Store {
agent_state_version INTEGER DEFAULT 1,
model TEXT,
effort TEXT,
model_reasoning_effort TEXT,
todos TEXT,
todos_updated_at INTEGER,
team_state TEXT,
Expand Down Expand Up @@ -333,6 +348,13 @@ export class Store {
}
}

private migrateFromV6ToV7(): void {
const columns = this.getSessionColumnNames()
if (!columns.has('model_reasoning_effort')) {
this.db.exec('ALTER TABLE sessions ADD COLUMN model_reasoning_effort TEXT')
}
}

private getSessionColumnNames(): Set<string> {
const rows = this.db.prepare('PRAGMA table_info(sessions)').all() as Array<{ name: string }>
return new Set(rows.map((row) => row.name))
Expand Down
10 changes: 8 additions & 2 deletions hub/src/store/sessionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getSessionsByNamespace,
setSessionEffort,
setSessionModel,
setSessionModelReasoningEffort,
setSessionTeamState,
setSessionTodos,
updateSessionAgentState,
Expand All @@ -29,9 +30,10 @@ export class SessionStore {
agentState: unknown,
namespace: string,
model?: string,
effort?: string
effort?: string,
modelReasoningEffort?: string
): StoredSession {
return getOrCreateSession(this.db, tag, metadata, agentState, namespace, model, effort)
return getOrCreateSession(this.db, tag, metadata, agentState, namespace, model, effort, modelReasoningEffort)
}

updateSessionMetadata(
Expand Down Expand Up @@ -69,6 +71,10 @@ export class SessionStore {
return setSessionEffort(this.db, id, effort, namespace, options)
}

setSessionModelReasoningEffort(id: string, modelReasoningEffort: string | null, namespace: string, options?: { touchUpdatedAt?: boolean }): boolean {
return setSessionModelReasoningEffort(this.db, id, modelReasoningEffort, namespace, options)
}

getSession(id: string): StoredSession | null {
return getSession(this.db, id)
}
Expand Down
43 changes: 41 additions & 2 deletions hub/src/store/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type DbSessionRow = {
agent_state_version: number
model: string | null
effort: string | null
model_reasoning_effort: string | null
todos: string | null
todos_updated_at: number | null
team_state: string | null
Expand All @@ -41,6 +42,7 @@ function toStoredSession(row: DbSessionRow): StoredSession {
agentStateVersion: row.agent_state_version,
model: row.model,
effort: row.effort,
modelReasoningEffort: row.model_reasoning_effort,
todos: safeJsonParse(row.todos),
todosUpdatedAt: row.todos_updated_at,
teamState: safeJsonParse(row.team_state),
Expand All @@ -58,7 +60,8 @@ export function getOrCreateSession(
agentState: unknown,
namespace: string,
model?: string,
effort?: string
effort?: string,
modelReasoningEffort?: string
): StoredSession {
const existing = db.prepare(
'SELECT * FROM sessions WHERE tag = ? AND namespace = ? ORDER BY created_at DESC LIMIT 1'
Expand All @@ -81,6 +84,7 @@ export function getOrCreateSession(
agent_state, agent_state_version,
model,
effort,
model_reasoning_effort,
todos, todos_updated_at,
active, active_at, seq
) VALUES (
Expand All @@ -89,6 +93,7 @@ export function getOrCreateSession(
@agent_state, 1,
@model,
@effort,
@model_reasoning_effort,
NULL, NULL,
0, NULL, 0
)
Expand All @@ -101,7 +106,8 @@ export function getOrCreateSession(
metadata: metadataJson,
agent_state: agentStateJson,
model: model ?? null,
effort: effort ?? null
effort: effort ?? null,
model_reasoning_effort: modelReasoningEffort ?? null
})

const row = getSession(db, id)
Expand Down Expand Up @@ -303,6 +309,39 @@ export function setSessionEffort(
}
}

export function setSessionModelReasoningEffort(
db: Database,
id: string,
modelReasoningEffort: string | null,
namespace: string,
options?: { touchUpdatedAt?: boolean }
): boolean {
const now = Date.now()
const touchUpdatedAt = options?.touchUpdatedAt === true

try {
const result = db.prepare(`
UPDATE sessions
SET model_reasoning_effort = @model_reasoning_effort,
updated_at = CASE WHEN @touch_updated_at = 1 THEN @updated_at ELSE updated_at END,
seq = seq + 1
WHERE id = @id
AND namespace = @namespace
AND model_reasoning_effort IS NOT @model_reasoning_effort
`).run({
id,
namespace,
model_reasoning_effort: modelReasoningEffort,
updated_at: now,
touch_updated_at: touchUpdatedAt ? 1 : 0
})

return result.changes === 1
} catch {
return false
}
}

export function getSession(db: Database, id: string): StoredSession | null {
const row = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as DbSessionRow | undefined
return row ? toStoredSession(row) : null
Expand Down
1 change: 1 addition & 0 deletions hub/src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type StoredSession = {
agentStateVersion: number
model: string | null
effort: string | null
modelReasoningEffort: string | null
todos: unknown | null
todosUpdatedAt: number | null
teamState: unknown | null
Expand Down
1 change: 1 addition & 0 deletions hub/src/sync/rpcGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export class RpcGateway {
permissionMode?: PermissionMode
model?: string | null
effort?: string | null
modelReasoningEffort?: string | null
collaborationMode?: CodexCollaborationMode
}
): Promise<unknown> {
Expand Down
28 changes: 26 additions & 2 deletions hub/src/sync/sessionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ export class SessionCache {
agentState: unknown,
namespace: string,
model?: string,
effort?: string
effort?: string,
modelReasoningEffort?: string
): Session {
const stored = this.store.sessions.getOrCreateSession(tag, metadata, agentState, namespace, model, effort)
const stored = this.store.sessions.getOrCreateSession(tag, metadata, agentState, namespace, model, effort, modelReasoningEffort)
return this.refreshSession(stored.id) ?? (() => { throw new Error('Failed to load session') })()
}

Expand Down Expand Up @@ -135,6 +136,7 @@ export class SessionCache {
teamState,
model: stored.model,
effort: stored.effort,
modelReasoningEffort: stored.modelReasoningEffort ?? undefined,
permissionMode: existing?.permissionMode,
collaborationMode: existing?.collaborationMode
}
Expand Down Expand Up @@ -265,6 +267,7 @@ export class SessionCache {
permissionMode?: PermissionMode
model?: string | null
effort?: string | null
modelReasoningEffort?: string | null
collaborationMode?: CodexCollaborationMode
}
): void {
Expand Down Expand Up @@ -298,6 +301,18 @@ export class SessionCache {
}
session.effort = config.effort
}
if (config.modelReasoningEffort !== undefined) {
const currentMre = session.modelReasoningEffort ?? null
if (config.modelReasoningEffort !== currentMre) {
const updated = this.store.sessions.setSessionModelReasoningEffort(sessionId, config.modelReasoningEffort, session.namespace, {
touchUpdatedAt: false
})
if (!updated) {
throw new Error('Failed to update session model reasoning effort')
}
}
session.modelReasoningEffort = config.modelReasoningEffort ?? undefined
}
if (config.collaborationMode !== undefined) {
session.collaborationMode = config.collaborationMode
}
Expand Down Expand Up @@ -407,6 +422,15 @@ export class SessionCache {
}
}

if (newStored.modelReasoningEffort === null && oldStored.modelReasoningEffort !== null) {
const updated = this.store.sessions.setSessionModelReasoningEffort(newSessionId, oldStored.modelReasoningEffort, namespace, {
touchUpdatedAt: false
})
if (!updated) {
throw new Error('Failed to preserve session model reasoning effort during merge')
}
}

if (oldStored.todos !== null && oldStored.todosUpdatedAt !== null) {
this.store.sessions.setSessionTodos(
newSessionId,
Expand Down
9 changes: 6 additions & 3 deletions hub/src/sync/syncEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,10 @@ export class SyncEngine {
agentState: unknown,
namespace: string,
model?: string,
effort?: string
effort?: string,
modelReasoningEffort?: string
): Session {
return this.sessionCache.getOrCreateSession(tag, metadata, agentState, namespace, model, effort)
return this.sessionCache.getOrCreateSession(tag, metadata, agentState, namespace, model, effort, modelReasoningEffort)
}

getOrCreateMachine(id: string, metadata: unknown, runnerState: unknown, namespace: string): Machine {
Expand Down Expand Up @@ -292,6 +293,7 @@ export class SyncEngine {
permissionMode?: PermissionMode
model?: string | null
effort?: string | null
modelReasoningEffort?: string | null
collaborationMode?: CodexCollaborationMode
}
): Promise<void> {
Expand All @@ -304,6 +306,7 @@ export class SyncEngine {
permissionMode?: Session['permissionMode']
model?: Session['model']
effort?: Session['effort']
modelReasoningEffort?: Session['modelReasoningEffort']
collaborationMode?: Session['collaborationMode']
}
}
Expand Down Expand Up @@ -404,7 +407,7 @@ export class SyncEngine {
metadata.path,
flavor,
session.model ?? undefined,
undefined,
session.modelReasoningEffort ?? undefined,
undefined,
undefined,
undefined,
Expand Down
Loading
Loading