diff --git a/README.md b/README.md index 2b9e256f8..39b539d2e 100644 --- a/README.md +++ b/README.md @@ -491,19 +491,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/docs/api-reference/openapi.json b/docs/api-reference/openapi.json index 0ab054a1d..6ff4249e0 100644 --- a/docs/api-reference/openapi.json +++ b/docs/api-reference/openapi.json @@ -2414,8 +2414,7 @@ "type" : "string" }, "data" : { - "properties" : { }, - "type" : "object" + "$ref" : "#/components/schemas/model.TaskData" }, "id" : { "type" : "string" @@ -2444,6 +2443,29 @@ }, "type" : "object" }, + "model.TaskData" : { + "properties" : { + "progresses" : { + "items" : { + "type" : "string" + }, + "type" : "array" + }, + "sop_thinking" : { + "type" : "string" + }, + "task_description" : { + "type" : "string" + }, + "user_preferences" : { + "items" : { + "type" : "string" + }, + "type" : "array" + } + }, + "type" : "object" + }, "serializer.Response" : { "properties" : { "code" : { diff --git a/docs/integrations/agno.mdx b/docs/integrations/agno.mdx index 67ca0ff32..f2447bd77 100644 --- a/docs/integrations/agno.mdx +++ b/docs/integrations/agno.mdx @@ -198,7 +198,7 @@ async def main(): print("Extracted tasks:") for task in tasks_response.items: - print(f"Task: {task.data['task_description']}") + print(f"Task: {task.data.task_description}") print(f"Status: {task.status}") if __name__ == "__main__": @@ -233,17 +233,17 @@ acontext_client.sessions.flush(session_id) tasks_response = acontext_client.sessions.get_tasks(session_id) for task in tasks_response.items: - print(f"Task: {task.data['task_description']}") + print(f"Task: {task.data.task_description}") print(f"Status: {task.status}") # Access progress updates if available - if "progresses" in task.data: - for progress in task.data["progresses"]: + if task.data.progresses: + for progress in task.data.progresses: print(f" Progress: {progress}") # Access user preferences if available - if "user_preferences" in task.data: - for pref in task.data["user_preferences"]: + if task.data.user_preferences: + for pref in task.data.user_preferences: print(f" Preference: {pref}") ``` diff --git a/docs/integrations/ai-sdk.mdx b/docs/integrations/ai-sdk.mdx index ffe82159c..b602ceabd 100644 --- a/docs/integrations/ai-sdk.mdx +++ b/docs/integrations/ai-sdk.mdx @@ -474,7 +474,7 @@ async function main(): Promise { console.log('Extracted tasks:'); for (const task of tasksResponse.items) { - console.log(`Task: ${task.data['task_description']}`); + console.log(`Task: ${task.data.task_description}`); console.log(`Status: ${task.status}`); } } @@ -517,19 +517,19 @@ await acontextClient.sessions.flush(sessionId); const tasksResponse = await acontextClient.sessions.getTasks(sessionId); for (const task of tasksResponse.items) { - console.log(`Task: ${task.data['task_description']}`); + console.log(`Task: ${task.data.task_description}`); console.log(`Status: ${task.status}`); // Access progress updates if available - if ('progresses' in task.data) { - for (const progress of task.data['progresses'] as any[]) { + if (task.data.progresses) { + for (const progress of task.data.progresses) { console.log(` Progress: ${progress}`); } } // Access user preferences if available - if ('user_preferences' in task.data) { - for (const pref of task.data['user_preferences'] as any[]) { + if (task.data.user_preferences) { + for (const pref of task.data.user_preferences) { console.log(` Preference: ${pref}`); } } diff --git a/docs/integrations/openai-python.mdx b/docs/integrations/openai-python.mdx index 89a85d549..3e6a5e5b6 100644 --- a/docs/integrations/openai-python.mdx +++ b/docs/integrations/openai-python.mdx @@ -332,7 +332,7 @@ async def main(): print("Extracted tasks:") for task in tasks_response.items: - print(f"Task: {task.data['task_description']}") + print(f"Task: {task.data.task_description}") print(f"Status: {task.status}") if __name__ == "__main__": @@ -370,17 +370,17 @@ acontext_client.sessions.flush(session_id) tasks_response = acontext_client.sessions.get_tasks(session_id) for task in tasks_response.items: - print(f"Task: {task.data['task_description']}") + print(f"Task: {task.data.task_description}") print(f"Status: {task.status}") # Access progress updates if available - if "progresses" in task.data: - for progress in task.data["progresses"]: + if task.data.progresses: + for progress in task.data.progresses: print(f" Progress: {progress}") # Access user preferences if available - if "user_preferences" in task.data: - for pref in task.data["user_preferences"]: + if task.data.user_preferences: + for pref in task.data.user_preferences: print(f" Preference: {pref}") ``` diff --git a/docs/integrations/openai-typescript.mdx b/docs/integrations/openai-typescript.mdx index faf2ec88f..3705320d6 100644 --- a/docs/integrations/openai-typescript.mdx +++ b/docs/integrations/openai-typescript.mdx @@ -397,7 +397,7 @@ async function main(): Promise { console.log('Extracted tasks:'); for (const task of tasksResponse.items) { - console.log(`Task: ${task.data['task_description']}`); + console.log(`Task: ${task.data.task_description}`); console.log(`Status: ${task.status}`); } } @@ -441,19 +441,19 @@ await acontextClient.sessions.flush(sessionId); const tasksResponse = await acontextClient.sessions.getTasks(sessionId); for (const task of tasksResponse.items) { - console.log(`Task: ${task.data['task_description']}`); + console.log(`Task: ${task.data.task_description}`); console.log(`Status: ${task.status}`); // Access progress updates if available - if ('progresses' in task.data) { - for (const progress of task.data['progresses'] as any[]) { + if (task.data.progresses) { + for (const progress of task.data.progresses) { console.log(` Progress: ${progress}`); } } // Access user preferences if available - if ('user_preferences' in task.data) { - for (const pref of task.data['user_preferences'] as any[]) { + if (task.data.user_preferences) { + for (const pref of task.data.user_preferences) { console.log(` Preference: ${pref}`); } } diff --git a/docs/integrations/openai_agent.mdx b/docs/integrations/openai_agent.mdx index c42278f39..339c972d5 100644 --- a/docs/integrations/openai_agent.mdx +++ b/docs/integrations/openai_agent.mdx @@ -277,7 +277,7 @@ async def session_1(session_id: str): print("Extracted tasks:") for task in tasks_response.items: - print(f"Task: {task.data['task_description']}") + print(f"Task: {task.data.task_description}") print(f"Status: {task.status}") async def main(): @@ -324,17 +324,17 @@ acontext_client.sessions.flush(session_id) tasks_response = acontext_client.sessions.get_tasks(session_id) for task in tasks_response.items: - print(f"Task: {task.data['task_description']}") + print(f"Task: {task.data.task_description}") print(f"Status: {task.status}") # Access progress updates if available - if "progresses" in task.data: - for progress in task.data["progresses"]: + if task.data.progresses: + for progress in task.data.progresses: print(f" Progress: {progress}") # Access user preferences if available - if "user_preferences" in task.data: - for pref in task.data["user_preferences"]: + if task.data.user_preferences: + for pref in task.data.user_preferences: print(f" Preference: {pref}") ``` diff --git a/docs/observe/agent_tasks.mdx b/docs/observe/agent_tasks.mdx index 390e50d2c..6fd25be05 100644 --- a/docs/observe/agent_tasks.mdx +++ b/docs/observe/agent_tasks.mdx @@ -87,19 +87,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available - if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + if task.data.progresses: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available - if "user_preferences" in task.data: + if task.data.user_preferences: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` @@ -187,47 +187,104 @@ After running this code, you'll see the tasks that Acontext automatically extrac ## Understanding Task Data -Each extracted task contains a `data` field with structured information captured from the conversation: +Each extracted task contains a `data` field with structured information captured from the conversation. The `data` field is a `TaskData` object with the following schema: -```python -{ - "progresses": [ + +```python Python +from acontext import TaskData + +# TaskData structure +task_data = TaskData( + task_description="Search for the latest news about iPhone 15 pro max", + progresses=[ "I searched for iPhone 15 Pro Max specifications and found the latest features", "I've initialized the Next.js project with the latest template" ], - "user_preferences": [ + user_preferences=[ "Focus on the camera capabilities and battery life", "Make sure the landing page is mobile-responsive" ] -} +) +``` + +```typescript TypeScript +import { TaskData } from '@acontext/acontext'; + +// TaskData structure +const taskData: TaskData = { + task_description: "Search for the latest news about iPhone 15 pro max", + progresses: [ + "I searched for iPhone 15 Pro Max specifications and found the latest features", + "I've initialized the Next.js project with the latest template" + ], + user_preferences: [ + "Focus on the camera capabilities and battery life", + "Make sure the landing page is mobile-responsive" + ] +}; ``` + + +### TaskData Fields + +- **`task_description`** (string, required): A clear description of the task +- **`progresses`** (array of strings, optional): Agent's narrative updates as it works through the task +- **`user_preferences`** (array of strings, optional): Specific requirements or preferences mentioned by the user ### Progress Tracking The `progresses` array captures the agent's narrative updates as it works through tasks. Each entry describes what the agent accomplished, written in first-person perspective. -```python -# Access progress updates + +```python Python +# Access progress updates using structured TaskData for task in tasks_response.items: - if "progresses" in task.data: + if task.data.progresses: print(f"Task {task.order} progress:") - for progress in task.data["progresses"]: + for progress in task.data.progresses: print(f" - {progress}") ``` +```typescript TypeScript +// Access progress updates using structured TaskData +for (const task of tasksResponse.items) { + if (task.data.progresses) { + console.log(`Task ${task.order} progress:`); + task.data.progresses.forEach(progress => { + console.log(` - ${progress}`); + }); + } +} +``` + + ### User Preferences The `user_preferences` array stores specific requirements or preferences the user mentioned for each task during the conversation. -```python -# Check user preferences for a task + +```python Python +# Check user preferences for a task using structured TaskData for task in tasks_response.items: - if "user_preferences" in task.data: + if task.data.user_preferences: print(f"Task {task.order} user preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` +```typescript TypeScript +// Check user preferences for a task using structured TaskData +for (const task of tasksResponse.items) { + if (task.data.user_preferences) { + console.log(`Task ${task.order} user preferences:`); + task.data.user_preferences.forEach(pref => { + console.log(` - ${pref}`); + }); + } +} +``` + + Progress and preferences are appended to tasks as the conversation continues. Early in a conversation, these arrays may be empty or contain only initial entries. @@ -277,13 +334,13 @@ for task in response.items: elif task.status == "running": print(f"Agent stuck on: Task {task.order}") # Check last progress update - if "progresses" in task.data and task.data["progresses"]: - print(f" Last progress: {task.data['progresses'][-1]}") + if task.data.progresses: + print(f" Last progress: {task.data.progresses[-1]}") elif task.status == "failed": print(f"Agent failed at: Task {task.order}") # Check what was done before failure - if "progresses" in task.data: - print(f" Completed steps: {len(task.data['progresses'])}") + if task.data.progresses: + print(f" Completed steps: {len(task.data.progresses)}") ``` @@ -313,16 +370,17 @@ response = client.sessions.get_tasks(session_id, time_desc=True) print("=== Agent Activity Report ===") for task in response.items: print(f"\n{task.created_at} | Task {task.order} | {task.status}") + print(f" Description: {task.data.task_description}") # Show progress summary - if "progresses" in task.data: - print(f" Progress entries: {len(task.data['progresses'])}") - if task.data["progresses"]: - print(f" Latest: {task.data['progresses'][-1]}") + if task.data.progresses: + print(f" Progress entries: {len(task.data.progresses)}") + print(f" Latest: {task.data.progresses[-1]}") # Show user requirements - if "user_preferences" in task.data and task.data["user_preferences"]: - print(f" User requirements: {', '.join(task.data['user_preferences'])}") + if task.data.user_preferences: + print(f" User requirements: {', '.join(task.data.user_preferences)}") + ``` diff --git a/readme/de/README.md b/readme/de/README.md index e88f74403..2c062df8d 100644 --- a/readme/de/README.md +++ b/readme/de/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/es/README.md b/readme/es/README.md index 517e8f4a6..8d262c63c 100644 --- a/readme/es/README.md +++ b/readme/es/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/fr/README.md b/readme/fr/README.md index d029c966e..73d74bdda 100644 --- a/readme/fr/README.md +++ b/readme/fr/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/ja/README.md b/readme/ja/README.md index 91782745f..dff119fd3 100644 --- a/readme/ja/README.md +++ b/readme/ja/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/ko/README.md b/readme/ko/README.md index 293959be3..4d895942c 100644 --- a/readme/ko/README.md +++ b/readme/ko/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/pt/README.md b/readme/pt/README.md index 199d802a3..da53b3618 100644 --- a/readme/pt/README.md +++ b/readme/pt/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/ru/README.md b/readme/ru/README.md index 64e889a30..332349cd3 100644 --- a/readme/ru/README.md +++ b/readme/ru/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/readme/zh/README.md b/readme/zh/README.md index d6e50a687..41f8070b9 100644 --- a/readme/zh/README.md +++ b/readme/zh/README.md @@ -492,19 +492,19 @@ print(tasks_response) for task in tasks_response.items: print(f"\nTask #{task.order}:") print(f" ID: {task.id}") - print(f" Title: {task.data['task_description']}") + print(f" Title: {task.data.task_description}") print(f" Status: {task.status}") # Show progress updates if available if "progresses" in task.data: - print(f" Progress updates: {len(task.data['progresses'])}") - for progress in task.data["progresses"]: + print(f" Progress updates: {len(task.data.progresses)}") + for progress in task.data.progresses: print(f" - {progress}") # Show user preferences if available if "user_preferences" in task.data: print(" User preferences:") - for pref in task.data["user_preferences"]: + for pref in task.data.user_preferences: print(f" - {pref}") ``` diff --git a/src/client/acontext-py/examples/task_data_usage.py b/src/client/acontext-py/examples/task_data_usage.py new file mode 100644 index 000000000..de012c62e --- /dev/null +++ b/src/client/acontext-py/examples/task_data_usage.py @@ -0,0 +1,120 @@ +""" +Example demonstrating the use of structured TaskData in the Acontext Python SDK. + +This example shows how to: +1. Retrieve tasks from a session +2. Access structured TaskData fields with proper type annotations +3. Work with task descriptions, progresses, user preferences, and SOP thinking +""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from acontext import AcontextClient, Task, TaskData + + +def resolve_credentials() -> tuple[str, str]: + """Get API credentials from environment variables.""" + api_key = os.getenv("ACONTEXT_API_KEY", "sk-ac-your-root-api-bearer-token") + base_url = os.getenv("ACONTEXT_BASE_URL", "http://localhost:8029/api/v1") + return api_key, base_url + + +def display_task_data(task: Task) -> None: + """Display structured TaskData with proper type annotations. + + Args: + task: Task object with structured TaskData + """ + print(f"\n{'='*60}") + print(f"Task #{task.order} (ID: {task.id})") + print(f"Status: {task.status}") + print(f"Planning: {task.is_planning}") + print(f"Space Digested: {task.space_digested}") + print(f"{'='*60}") + + # Access structured TaskData fields with type safety + data: TaskData = task.data + + print("\nšŸ“ Task Description:") + print(f" {data.task_description}") + + if data.progresses: + print(f"\nāœ… Progress Updates ({len(data.progresses)}):") + for i, progress in enumerate(data.progresses, 1): + print(f" {i}. {progress}") + + if data.user_preferences: + print(f"\nāš™ļø User Preferences ({len(data.user_preferences)}):") + for i, pref in enumerate(data.user_preferences, 1): + print(f" {i}. {pref}") + + if data.sop_thinking: + print("\nšŸ’­ SOP Thinking:") + print(f" {data.sop_thinking}") + + print("\nā° Timestamps:") + print(f" Created: {task.created_at}") + print(f" Updated: {task.updated_at}") + + +def main() -> None: + """Main function to demonstrate TaskData usage.""" + api_key, base_url = resolve_credentials() + + # Get session ID from environment or use a default + session_id = os.getenv("ACONTEXT_SESSION_ID") + if not session_id: + print("āš ļø Please set ACONTEXT_SESSION_ID environment variable") + print(" Example: export ACONTEXT_SESSION_ID='your-session-uuid'") + return + + with AcontextClient(api_key=api_key, base_url=base_url) as client: + print(f"šŸ” Fetching tasks for session: {session_id}") + + # Get tasks with structured TaskData + result = client.sessions.get_tasks( + session_id, limit=20, time_desc=True # Most recent first + ) + + print(f"\nšŸ“Š Found {len(result.items)} task(s)") + print(f" Has more: {result.has_more}") + if result.next_cursor: + print(f" Next cursor: {result.next_cursor[:50]}...") + + # Display each task with structured data + for task in result.items: + display_task_data(task) + + # Example: Filter tasks by status + print(f"\n{'='*60}") + print("Task Summary by Status:") + print(f"{'='*60}") + + status_counts = {} + for task in result.items: + status_counts[task.status] = status_counts.get(task.status, 0) + 1 + + for status, count in sorted(status_counts.items()): + print(f" {status.upper()}: {count}") + + # Example: Show tasks with progresses + tasks_with_progress = [t for t in result.items if t.data.progresses] + print(f"\nšŸ“ˆ Tasks with progress updates: {len(tasks_with_progress)}") + + # Example: Show tasks with user preferences + tasks_with_prefs = [t for t in result.items if t.data.user_preferences] + print(f"āš™ļø Tasks with user preferences: {len(tasks_with_prefs)}") + + # Example: Show tasks with SOP thinking + tasks_with_sop = [t for t in result.items if t.data.sop_thinking] + print(f"šŸ’­ Tasks with SOP thinking: {len(tasks_with_sop)}") + + +if __name__ == "__main__": + main() diff --git a/src/client/acontext-py/pyproject.toml b/src/client/acontext-py/pyproject.toml index ee3f1212f..a09461896 100644 --- a/src/client/acontext-py/pyproject.toml +++ b/src/client/acontext-py/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "acontext" -version = "0.0.8" +version = "0.0.9" description = "Python SDK for the Acontext API" readme = "README.md" requires-python = ">=3.10" diff --git a/src/client/acontext-py/src/acontext/__init__.py b/src/client/acontext-py/src/acontext/__init__.py index 4f86231da..a7f5dd58f 100644 --- a/src/client/acontext-py/src/acontext/__init__.py +++ b/src/client/acontext-py/src/acontext/__init__.py @@ -19,6 +19,7 @@ SessionsAPI, SpacesAPI, ) +from .types import Task, TaskData __all__ = [ "AcontextClient", @@ -36,6 +37,8 @@ "AsyncBlocksAPI", "AsyncSessionsAPI", "AsyncSpacesAPI", + "Task", + "TaskData", "__version__", ] diff --git a/src/client/acontext-py/src/acontext/types/__init__.py b/src/client/acontext-py/src/acontext/types/__init__.py index b6a0faab1..c89f90e66 100644 --- a/src/client/acontext-py/src/acontext/types/__init__.py +++ b/src/client/acontext-py/src/acontext/types/__init__.py @@ -20,6 +20,7 @@ PublicURL, Session, Task, + TaskData, TokenCounts, ) from .block import Block @@ -58,6 +59,7 @@ "PublicURL", "Session", "Task", + "TaskData", "TokenCounts", # Space types "ExperienceConfirmation", diff --git a/src/client/acontext-py/src/acontext/types/session.py b/src/client/acontext-py/src/acontext/types/session.py index 7a33f23e1..2855586b3 100644 --- a/src/client/acontext-py/src/acontext/types/session.py +++ b/src/client/acontext-py/src/acontext/types/session.py @@ -120,6 +120,25 @@ class Session(BaseModel): updated_at: str = Field(..., description="ISO 8601 formatted update timestamp") +class TaskData(BaseModel): + """Task data model representing the structured data stored in a task. + + This schema matches the TaskData model in acontext_core/schema/session/task.py + and the Go API TaskData struct. + """ + + task_description: str = Field(..., description="Description of the task") + progresses: list[str] | None = Field( + None, description="List of progress updates for the task" + ) + user_preferences: list[str] | None = Field( + None, description="List of user preferences related to the task" + ) + sop_thinking: str | None = Field( + None, description="Standard Operating Procedure thinking notes" + ) + + class Task(BaseModel): """Task model representing a task in a session.""" @@ -127,7 +146,7 @@ class Task(BaseModel): session_id: str = Field(..., description="Session UUID") project_id: str = Field(..., description="Project UUID") order: int = Field(..., description="Task order") - data: dict[str, Any] = Field(..., description="Task data dictionary") + data: TaskData = Field(..., description="Structured task data") status: str = Field( ..., description="Task status: 'success', 'failed', 'running', or 'pending'", diff --git a/src/client/acontext-py/tests/test_client.py b/src/client/acontext-py/tests/test_client.py index 168f23b2e..3753a88be 100644 --- a/src/client/acontext-py/tests/test_client.py +++ b/src/client/acontext-py/tests/test_client.py @@ -498,6 +498,53 @@ def test_sessions_get_tasks_with_filters(mock_request, client: AcontextClient) - assert hasattr(result, "has_more") +@patch("acontext.client.AcontextClient.request") +def test_sessions_get_tasks_with_task_data( + mock_request, client: AcontextClient +) -> None: + """Test that get_tasks properly deserializes TaskData with structured fields.""" + mock_request.return_value = { + "items": [ + { + "id": "123e4567-e89b-12d3-a456-426614174000", + "session_id": "123e4567-e89b-12d3-a456-426614174001", + "project_id": "123e4567-e89b-12d3-a456-426614174002", + "order": 1, + "data": { + "task_description": "Implement user authentication", + "progresses": ["Created login form", "Added JWT validation"], + "user_preferences": ["Use OAuth2", "Enable 2FA"], + "sop_thinking": "Follow security best practices", + }, + "status": "running", + "is_planning": False, + "space_digested": True, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + } + ], + "has_more": False, + } + + result = client.sessions.get_tasks("session-id") + + mock_request.assert_called_once() + # Verify the result structure + assert len(result.items) == 1 + task = result.items[0] + + # Verify Task fields + assert task.id == "123e4567-e89b-12d3-a456-426614174000" + assert task.status == "running" + assert task.order == 1 + + # Verify TaskData is properly typed and accessible + assert task.data.task_description == "Implement user authentication" + assert task.data.progresses == ["Created login form", "Added JWT validation"] + assert task.data.user_preferences == ["Use OAuth2", "Enable 2FA"] + assert task.data.sop_thinking == "Follow security best practices" + + @patch("acontext.client.AcontextClient.request") def test_sessions_get_learning_status(mock_request, client: AcontextClient) -> None: mock_request.return_value = { diff --git a/src/client/acontext-ts/examples/task-data-usage.ts b/src/client/acontext-ts/examples/task-data-usage.ts new file mode 100644 index 000000000..002922999 --- /dev/null +++ b/src/client/acontext-ts/examples/task-data-usage.ts @@ -0,0 +1,142 @@ +/** + * Example demonstrating the use of structured TaskData in the Acontext TypeScript SDK. + * + * This example shows how to: + * 1. Retrieve tasks from a session + * 2. Access structured TaskData fields with proper type annotations + * 3. Work with task descriptions, progresses, user preferences, and SOP thinking + */ + +import { AcontextClient, Task, TaskData } from '@acontext/acontext'; + +/** + * Get API credentials from environment variables + */ +function resolveCredentials(): { apiKey: string; baseUrl: string } { + const apiKey = process.env.ACONTEXT_API_KEY || 'sk-ac-your-root-api-bearer-token'; + const baseUrl = process.env.ACONTEXT_BASE_URL || 'http://localhost:8029/api/v1'; + return { apiKey, baseUrl }; +} + +/** + * Display structured TaskData with proper type annotations + */ +function displayTaskData(task: Task): void { + console.log('\n' + '='.repeat(60)); + console.log(`Task #${task.order} (ID: ${task.id})`); + console.log(`Status: ${task.status}`); + console.log(`Planning: ${task.is_planning}`); + console.log(`Space Digested: ${task.space_digested}`); + console.log('='.repeat(60)); + + // Access structured TaskData fields with type safety + const data: TaskData = task.data; + + console.log('\nšŸ“ Task Description:'); + console.log(` ${data.task_description}`); + + if (data.progresses && data.progresses.length > 0) { + console.log(`\nāœ… Progress Updates (${data.progresses.length}):`); + data.progresses.forEach((progress, i) => { + console.log(` ${i + 1}. ${progress}`); + }); + } + + if (data.user_preferences && data.user_preferences.length > 0) { + console.log(`\nāš™ļø User Preferences (${data.user_preferences.length}):`); + data.user_preferences.forEach((pref, i) => { + console.log(` ${i + 1}. ${pref}`); + }); + } + + if (data.sop_thinking) { + console.log('\nšŸ’­ SOP Thinking:'); + console.log(` ${data.sop_thinking}`); + } + + console.log('\nā° Timestamps:'); + console.log(` Created: ${task.created_at}`); + console.log(` Updated: ${task.updated_at}`); +} + +/** + * Main function to demonstrate TaskData usage + */ +async function main(): Promise { + const { apiKey, baseUrl } = resolveCredentials(); + + // Get session ID from environment or use a default + const sessionId = process.env.ACONTEXT_SESSION_ID; + if (!sessionId) { + console.log('āš ļø Please set ACONTEXT_SESSION_ID environment variable'); + console.log(" Example: export ACONTEXT_SESSION_ID='your-session-uuid'"); + return; + } + + const client = new AcontextClient({ apiKey, baseUrl }); + + try { + console.log(`šŸ” Fetching tasks for session: ${sessionId}`); + + // Get tasks with structured TaskData + const result = await client.sessions.getTasks(sessionId, { + limit: 20, + timeDesc: true, // Most recent first + }); + + console.log(`\nšŸ“Š Found ${result.items.length} task(s)`); + console.log(` Has more: ${result.has_more}`); + if (result.next_cursor) { + console.log(` Next cursor: ${result.next_cursor.substring(0, 50)}...`); + } + + // Display each task with structured data + for (const task of result.items) { + displayTaskData(task); + } + + // Example: Filter tasks by status + console.log('\n' + '='.repeat(60)); + console.log('Task Summary by Status:'); + console.log('='.repeat(60)); + + const statusCounts: Record = {}; + for (const task of result.items) { + statusCounts[task.status] = (statusCounts[task.status] || 0) + 1; + } + + Object.entries(statusCounts) + .sort(([a], [b]) => a.localeCompare(b)) + .forEach(([status, count]) => { + console.log(` ${status.toUpperCase()}: ${count}`); + }); + + // Example: Show tasks with progresses + const tasksWithProgress = result.items.filter( + (t) => t.data.progresses && t.data.progresses.length > 0 + ); + console.log(`\nšŸ“ˆ Tasks with progress updates: ${tasksWithProgress.length}`); + + // Example: Show tasks with user preferences + const tasksWithPrefs = result.items.filter( + (t) => t.data.user_preferences && t.data.user_preferences.length > 0 + ); + console.log(`āš™ļø Tasks with user preferences: ${tasksWithPrefs.length}`); + + // Example: Show tasks with SOP thinking + const tasksWithSop = result.items.filter((t) => t.data.sop_thinking); + console.log(`šŸ’­ Tasks with SOP thinking: ${tasksWithSop.length}`); + } catch (error) { + console.error('Error:', error); + throw error; + } +} + +// Run the example +if (require.main === module) { + main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); + }); +} + diff --git a/src/client/acontext-ts/package.json b/src/client/acontext-ts/package.json index cf88b8ba4..edfd7eb5c 100644 --- a/src/client/acontext-ts/package.json +++ b/src/client/acontext-ts/package.json @@ -1,6 +1,6 @@ { "name": "@acontext/acontext", - "version": "0.0.10", + "version": "0.0.11", "description": "TypeScript SDK for the Acontext API", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/client/acontext-ts/src/types/session.ts b/src/client/acontext-ts/src/types/session.ts index e8aa2a067..1cd512f0d 100644 --- a/src/client/acontext-ts/src/types/session.ts +++ b/src/client/acontext-ts/src/types/session.ts @@ -52,12 +52,26 @@ export const SessionSchema = z.object({ export type Session = z.infer; +/** + * TaskData represents the structured data stored in a task. + * This schema matches the TaskData model in acontext_core/schema/session/task.py + * and the Go API TaskData struct. + */ +export const TaskDataSchema = z.object({ + task_description: z.string(), + progresses: z.array(z.string()).nullable().optional(), + user_preferences: z.array(z.string()).nullable().optional(), + sop_thinking: z.string().nullable().optional(), +}); + +export type TaskData = z.infer; + export const TaskSchema = z.object({ id: z.string(), session_id: z.string(), project_id: z.string(), order: z.number(), - data: z.record(z.string(), z.unknown()), + data: TaskDataSchema, status: z.string(), is_planning: z.boolean(), space_digested: z.boolean(), diff --git a/src/server/api/go/docs/docs.go b/src/server/api/go/docs/docs.go index f9691de12..d549ad405 100644 --- a/src/server/api/go/docs/docs.go +++ b/src/server/api/go/docs/docs.go @@ -3062,7 +3062,7 @@ const docTemplate = `{ "type": "string" }, "data": { - "type": "object" + "$ref": "#/definitions/model.TaskData" }, "id": { "type": "string" @@ -3090,6 +3090,29 @@ const docTemplate = `{ } } }, + "model.TaskData": { + "type": "object", + "properties": { + "progresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "sop_thinking": { + "type": "string" + }, + "task_description": { + "type": "string" + }, + "user_preferences": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "serializer.Response": { "type": "object", "properties": { diff --git a/src/server/api/go/docs/swagger.json b/src/server/api/go/docs/swagger.json index 1a2ab49ef..77c162428 100644 --- a/src/server/api/go/docs/swagger.json +++ b/src/server/api/go/docs/swagger.json @@ -3059,7 +3059,7 @@ "type": "string" }, "data": { - "type": "object" + "$ref": "#/definitions/model.TaskData" }, "id": { "type": "string" @@ -3087,6 +3087,29 @@ } } }, + "model.TaskData": { + "type": "object", + "properties": { + "progresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "sop_thinking": { + "type": "string" + }, + "task_description": { + "type": "string" + }, + "user_preferences": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "serializer.Response": { "type": "object", "properties": { diff --git a/src/server/api/go/docs/swagger.yaml b/src/server/api/go/docs/swagger.yaml index 0e4d07efa..a29414f91 100644 --- a/src/server/api/go/docs/swagger.yaml +++ b/src/server/api/go/docs/swagger.yaml @@ -339,7 +339,7 @@ definitions: created_at: type: string data: - type: object + $ref: '#/definitions/model.TaskData' id: type: string is_planning: @@ -357,6 +357,21 @@ definitions: updated_at: type: string type: object + model.TaskData: + properties: + progresses: + items: + type: string + type: array + sop_thinking: + type: string + task_description: + type: string + user_preferences: + items: + type: string + type: array + type: object serializer.Response: properties: code: diff --git a/src/server/api/go/internal/modules/model/task.go b/src/server/api/go/internal/modules/model/task.go index 6ef1f4f12..fd17d2986 100644 --- a/src/server/api/go/internal/modules/model/task.go +++ b/src/server/api/go/internal/modules/model/task.go @@ -1,10 +1,12 @@ package model import ( + "database/sql/driver" + "encoding/json" + "errors" "time" "github.com/google/uuid" - "gorm.io/datatypes" ) type Task struct { @@ -12,11 +14,11 @@ type Task struct { SessionID uuid.UUID `gorm:"type:uuid;not null;index:ix_task_session_id;index:ix_task_session_id_task_id,priority:1;index:ix_task_session_id_status,priority:1;uniqueIndex:uq_session_id_order,priority:1" json:"session_id"` ProjectID uuid.UUID `gorm:"type:uuid;not null;index:ix_task_project_id" json:"project_id"` - Order int `gorm:"not null;uniqueIndex:uq_session_id_order,priority:2" json:"order"` - Data datatypes.JSONMap `gorm:"type:jsonb;not null" swaggertype:"object" json:"data"` - Status string `gorm:"type:text;not null;default:'pending';check:status IN ('success','failed','running','pending');index:ix_task_session_id_status,priority:2" json:"status"` - IsPlanning bool `gorm:"not null;default:false" json:"is_planning"` - SpaceDigested bool `gorm:"not null;default:false" json:"space_digested"` + Order int `gorm:"not null;uniqueIndex:uq_session_id_order,priority:2" json:"order"` + Data TaskData `gorm:"type:jsonb;not null;serializer:json" json:"data"` + Status string `gorm:"type:text;not null;default:'pending';check:status IN ('success','failed','running','pending');index:ix_task_session_id_status,priority:2" json:"status"` + IsPlanning bool `gorm:"not null;default:false" json:"is_planning"` + SpaceDigested bool `gorm:"not null;default:false" json:"space_digested"` CreatedAt time.Time `gorm:"autoCreateTime;not null;default:CURRENT_TIMESTAMP" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime;not null;default:CURRENT_TIMESTAMP" json:"updated_at"` @@ -31,4 +33,30 @@ type Task struct { Messages []Message `gorm:"constraint:OnDelete:SET NULL,OnUpdate:CASCADE;" json:"-"` } +// TaskData represents the structured data stored in Task.Data field +// This schema matches the Python TaskData model in acontext_core/schema/session/task.py +type TaskData struct { + TaskDescription string `json:"task_description"` + Progresses []string `json:"progresses,omitempty"` + UserPreferences []string `json:"user_preferences,omitempty"` + SOPThinking string `json:"sop_thinking,omitempty"` +} + +// Scan implements the sql.Scanner interface for TaskData +func (td *TaskData) Scan(value interface{}) error { + if value == nil { + return nil + } + bytes, ok := value.([]byte) + if !ok { + return errors.New("failed to unmarshal JSONB value") + } + return json.Unmarshal(bytes, td) +} + +// Value implements the driver.Valuer interface for TaskData +func (td TaskData) Value() (driver.Value, error) { + return json.Marshal(td) +} + func (Task) TableName() string { return "tasks" } diff --git a/src/server/core/tests/service/test_task_data.py b/src/server/core/tests/service/test_task_data.py index dec5b7bd6..f9593be5b 100644 --- a/src/server/core/tests/service/test_task_data.py +++ b/src/server/core/tests/service/test_task_data.py @@ -223,7 +223,8 @@ async def test_update_order_success(self): async with db_client.get_session_context() as session: # Create test data project = Project( - secret_key_hmac="test_key_hmac_update_order", secret_key_hash_phc="test_key_hash_update_order" + secret_key_hmac="test_key_hmac_update_order", + secret_key_hash_phc="test_key_hash_update_order", ) session.add(project) await session.flush()