Skip to content

Commit f79adc2

Browse files
committed
fix: minor fixes
1 parent 358ee29 commit f79adc2

11 files changed

Lines changed: 113 additions & 30 deletions

File tree

.vscode/settings.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/client/acontext-py/src/acontext/types/session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ class Session(BaseModel):
177177
disable_task_tracking: bool = Field(
178178
False, description="Whether task tracking is disabled for this session"
179179
)
180+
display_title: str | None = Field(
181+
None, description="Optional generated display title for the session"
182+
)
180183
configs: dict[str, Any] | None = Field(
181184
None, description="Session configuration dictionary"
182185
)

src/client/acontext-py/tests/test_async_client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,26 @@ async def test_async_sessions_create_with_use_uuid_and_user(
150150
assert kwargs["json_data"]["configs"] == {"agent": "bot1"}
151151

152152

153+
@patch("acontext.async_client.AcontextAsyncClient.request", new_callable=AsyncMock)
154+
@pytest.mark.asyncio
155+
async def test_async_sessions_create_parses_display_title(
156+
mock_request, async_client: AcontextAsyncClient
157+
) -> None:
158+
"""Test that display_title from API is available on Session model."""
159+
mock_request.return_value = {
160+
"id": "session-id",
161+
"project_id": "project-id",
162+
"display_title": "Plan migration rollout",
163+
"configs": {},
164+
"created_at": "2024-01-01T00:00:00Z",
165+
"updated_at": "2024-01-01T00:00:00Z",
166+
}
167+
168+
session = await async_client.sessions.create()
169+
170+
assert session.display_title == "Plan migration rollout"
171+
172+
153173
@patch("acontext.async_client.AcontextAsyncClient.request", new_callable=AsyncMock)
154174
@pytest.mark.asyncio
155175
async def test_async_store_message_with_files_uses_multipart_payload(

src/client/acontext-py/tests/test_client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,25 @@ def test_sessions_create_with_use_uuid_and_user(
461461
assert kwargs["json_data"]["configs"] == {"agent": "bot1"}
462462

463463

464+
@patch("acontext.client.AcontextClient.request")
465+
def test_sessions_create_parses_display_title(
466+
mock_request, client: AcontextClient
467+
) -> None:
468+
"""Test that display_title from API is available on Session model."""
469+
mock_request.return_value = {
470+
"id": "session-id",
471+
"project_id": "project-id",
472+
"display_title": "Plan migration rollout",
473+
"configs": {},
474+
"created_at": "2024-01-01T00:00:00Z",
475+
"updated_at": "2024-01-01T00:00:00Z",
476+
}
477+
478+
session = client.sessions.create()
479+
480+
assert session.display_title == "Plan migration rollout"
481+
482+
464483
@patch("acontext.client.AcontextClient.request")
465484
def test_sessions_list_filter_by_configs(mock_request, client: AcontextClient) -> None:
466485
"""Test that filter_by_configs is JSON-encoded and sent to API."""

src/client/acontext-ts/src/types/session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export const SessionSchema = z.object({
4949
project_id: z.string(),
5050
user_id: z.string().nullable().optional(),
5151
disable_task_tracking: z.boolean(),
52+
display_title: z.string().nullable().optional(),
5253
configs: z.record(z.string(), z.unknown()).nullable(),
5354
created_at: z.string(),
5455
updated_at: z.string(),

src/client/acontext-ts/tests/client.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,16 @@ describe('AcontextClient Unit Tests', () => {
123123
expect(session.id).toBe(customUuid);
124124
});
125125

126+
test('should parse display_title in session response', async () => {
127+
const createdSession = mockSession({
128+
display_title: 'Plan migration rollout',
129+
});
130+
client.mock().onPost('/session', () => createdSession);
131+
132+
const session = await client.sessions.create();
133+
expect(session.display_title).toBe('Plan migration rollout');
134+
});
135+
126136
test('should store a message in acontext format', async () => {
127137
const sessionId = 'test-session-id';
128138
const storedMessage = mockMessage({

src/client/acontext-ts/tests/mocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export function mockSession(overrides?: Partial<{
195195
project_id: string;
196196
user_id: string | null;
197197
disable_task_tracking: boolean;
198+
display_title: string | null;
198199
configs: Record<string, unknown> | null;
199200
created_at: string;
200201
updated_at: string;
@@ -205,6 +206,7 @@ export function mockSession(overrides?: Partial<{
205206
project_id: overrides?.project_id ?? mockId(),
206207
user_id: overrides?.user_id ?? null,
207208
disable_task_tracking: overrides?.disable_task_tracking ?? false,
209+
display_title: overrides?.display_title ?? null,
208210
configs: overrides?.configs ?? {},
209211
created_at: overrides?.created_at ?? now,
210212
updated_at: overrides?.updated_at ?? now,

src/server/core/acontext_core/infra/db.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from ..schema.orm import ORM_BASE
1919
from ..env import LOG as logger
2020
from ..env import DEFAULT_CORE_CONFIG
21+
from .schema_migrations import apply_runtime_schema_patches
2122

2223

2324
class DatabaseClient:
@@ -196,9 +197,16 @@ async def create_tables(self) -> None:
196197
logger.info("pgvector extension init")
197198
async with self.engine.begin() as conn:
198199
await conn.run_sync(ORM_BASE.metadata.create_all)
200+
await self._apply_schema_migrations()
199201

200202
self._table_created = True
201203

204+
async def _apply_schema_migrations(self) -> None:
205+
"""Apply idempotent schema patches for existing deployments."""
206+
async with self.get_session_context() as db_session:
207+
patch_names = await apply_runtime_schema_patches(db_session)
208+
logger.info(f"Schema patches ensured: {', '.join(patch_names)}")
209+
202210
async def drop_tables(self) -> None:
203211
"""Drop all tables defined in the ORM models."""
204212
async with self.engine.begin() as conn:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from sqlalchemy import text
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
DISPLAY_TITLE_COLUMN_PATCH_NAME = "sessions.display_title"
5+
DISPLAY_TITLE_COLUMN_PATCH_SQL = text(
6+
"ALTER TABLE sessions ADD COLUMN IF NOT EXISTS display_title TEXT;"
7+
)
8+
9+
10+
async def apply_runtime_schema_patches(db_session: AsyncSession) -> list[str]:
11+
"""Apply idempotent runtime schema patches for existing deployments."""
12+
applied_patch_names: list[str] = []
13+
14+
await db_session.execute(DISPLAY_TITLE_COLUMN_PATCH_SQL)
15+
applied_patch_names.append(DISPLAY_TITLE_COLUMN_PATCH_NAME)
16+
17+
return applied_patch_names

src/server/core/acontext_core/service/controller/message.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ async def process_session_pending_message(
133133

134134
pending_message_ids = None
135135
try:
136-
async with DB_CLIENT.get_session_context() as session:
136+
async with DB_CLIENT.get_session_context() as db_session:
137137
r = await MD.get_message_ids(
138-
session,
138+
db_session,
139139
session_id,
140140
limit=(
141141
project_config.project_session_message_buffer_max_overflow
@@ -153,22 +153,22 @@ async def process_session_pending_message(
153153
f"Project {project_id} has disabled new task creation, skip"
154154
)
155155
await MD.update_message_status_to(
156-
session, pending_message_ids, TaskStatus.FAILED
156+
db_session, pending_message_ids, TaskStatus.FAILED
157157
)
158158
return Result.resolve(None)
159159
await MD.update_message_status_to(
160-
session, pending_message_ids, TaskStatus.RUNNING
160+
db_session, pending_message_ids, TaskStatus.RUNNING
161161
)
162162
LOG.info(f"Unpending {len(pending_message_ids)} session messages to process")
163163

164-
async with DB_CLIENT.get_session_context() as session:
165-
r = await MD.fetch_messages_data_by_ids(session, pending_message_ids)
164+
async with DB_CLIENT.get_session_context() as db_session:
165+
r = await MD.fetch_messages_data_by_ids(db_session, pending_message_ids)
166166
messages, eil = r.unpack()
167167
if eil:
168168
return r
169169

170170
r = await MD.fetch_previous_messages_by_datetime(
171-
session,
171+
db_session,
172172
session_id,
173173
messages[0].created_at,
174174
limit=project_config.project_session_message_use_previous_messages_turns,
@@ -181,7 +181,9 @@ async def process_session_pending_message(
181181
]
182182
first_user_message_text = None
183183
try:
184-
r = await SD.should_generate_session_display_title(session, session_id)
184+
r = await SD.should_generate_session_display_title(
185+
db_session, session_id
186+
)
185187
should_generate_title, eil = r.unpack()
186188
if eil:
187189
raise ValueError(eil.errmsg)
@@ -217,8 +219,10 @@ async def process_session_pending_message(
217219
if first_user_message_text is not None:
218220
try:
219221
title_candidate = None
220-
r = await generate_session_title_candidate(first_user_message_text)
221-
title_candidate_raw, eil = r.unpack()
222+
title_result = await generate_session_title_candidate(
223+
first_user_message_text
224+
)
225+
title_candidate_raw, eil = title_result.unpack()
222226
if eil:
223227
raise ValueError(eil.errmsg)
224228

@@ -236,9 +240,9 @@ async def process_session_pending_message(
236240
)
237241

238242
if title_candidate is not None:
239-
async with DB_CLIENT.get_session_context() as session:
243+
async with DB_CLIENT.get_session_context() as db_session:
240244
r = await SD.update_session_display_title(
241-
session, session_id, title_candidate
245+
db_session, session_id, title_candidate
242246
)
243247
_, eil = r.unpack()
244248
if eil:
@@ -249,7 +253,7 @@ async def process_session_pending_message(
249253
f"Skip title generation/persist for session {session_id}: {title_err}"
250254
)
251255

252-
r = await AT.task_agent_curd(
256+
agent_result = await AT.task_agent_curd(
253257
project_id,
254258
session_id,
255259
messages_data,
@@ -258,21 +262,22 @@ async def process_session_pending_message(
258262
)
259263

260264
after_status = TaskStatus.SUCCESS
261-
if not r.ok():
265+
if not agent_result.ok():
262266
after_status = TaskStatus.FAILED
263-
async with DB_CLIENT.get_session_context() as session:
267+
async with DB_CLIENT.get_session_context() as db_session:
264268
await MD.update_message_status_to(
265-
session, pending_message_ids, after_status
269+
db_session, pending_message_ids, after_status
266270
)
267-
return r
271+
272+
return agent_result
268273
except Exception as e:
269274
if pending_message_ids is None:
270275
raise e
271276
LOG.error(
272277
f"Exception while processing session pending message: {e}, rollback {len(pending_message_ids)} message status to failed"
273278
)
274-
async with DB_CLIENT.get_session_context() as session:
279+
async with DB_CLIENT.get_session_context() as db_session:
275280
await MD.update_message_status_to(
276-
session, pending_message_ids, TaskStatus.FAILED
281+
db_session, pending_message_ids, TaskStatus.FAILED
277282
)
278283
raise e

0 commit comments

Comments
 (0)