feat: add Session, Message, ToolCall models with migrations
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
102
src/openclaw/migrations/0001_initial.py
Normal file
102
src/openclaw/migrations/0001_initial.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# Generated by Django 5.2.12 on 2026-04-05 06:49
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Session',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('session_id', models.CharField(max_length=64)),
|
||||
('agent_name', models.CharField(max_length=128)),
|
||||
('source_node', models.CharField(max_length=64)),
|
||||
('session_version', models.IntegerField(default=0)),
|
||||
('model_provider', models.CharField(blank=True, default='', max_length=64)),
|
||||
('model_id', models.CharField(blank=True, default='', max_length=128)),
|
||||
('thinking_level', models.CharField(blank=True, default='', max_length=64)),
|
||||
('start_time', models.DateTimeField(blank=True, null=True)),
|
||||
('end_time', models.DateTimeField(blank=True, null=True)),
|
||||
('cwd', models.CharField(blank=True, default='', max_length=512)),
|
||||
('total_tokens', models.IntegerField(default=0)),
|
||||
('total_cost', models.FloatField(default=0.0)),
|
||||
('message_count', models.IntegerField(default=0)),
|
||||
('tool_call_count', models.IntegerField(default=0)),
|
||||
('error_count', models.IntegerField(default=0)),
|
||||
('raw_file_path', models.TextField(blank=True, default='')),
|
||||
('pushed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('status', models.CharField(default='active', max_length=16)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'sessions',
|
||||
'ordering': ['-start_time'],
|
||||
'unique_together': {('session_id', 'agent_name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('message_id', models.CharField(max_length=128)),
|
||||
('parent_id', models.CharField(blank=True, default='', max_length=128)),
|
||||
('seq', models.IntegerField(default=0)),
|
||||
('role', models.CharField(max_length=32)),
|
||||
('content_text', models.TextField(blank=True, default='')),
|
||||
('raw_content', models.JSONField(blank=True, default=list)),
|
||||
('raw_message', models.JSONField(blank=True, default=dict)),
|
||||
('timestamp', models.DateTimeField()),
|
||||
('model', models.CharField(blank=True, default='', max_length=128)),
|
||||
('provider', models.CharField(blank=True, default='', max_length=64)),
|
||||
('stop_reason', models.CharField(blank=True, default='', max_length=64)),
|
||||
('tokens_input', models.IntegerField(default=0)),
|
||||
('tokens_output', models.IntegerField(default=0)),
|
||||
('tokens_cache_read', models.IntegerField(default=0)),
|
||||
('tokens_cache_write', models.IntegerField(default=0)),
|
||||
('tokens_total', models.IntegerField(default=0)),
|
||||
('cost_total', models.FloatField(default=0.0)),
|
||||
('tool_call_id', models.CharField(blank=True, default='', max_length=128)),
|
||||
('tool_name', models.CharField(blank=True, default='', max_length=128)),
|
||||
('is_error', models.BooleanField(default=False)),
|
||||
('exit_code', models.IntegerField(blank=True, null=True)),
|
||||
('duration_ms', models.IntegerField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='openclaw.session')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'messages',
|
||||
'ordering': ['seq'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ToolCall',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('tool_call_id', models.CharField(max_length=128)),
|
||||
('tool_name', models.CharField(max_length=128)),
|
||||
('arguments', models.JSONField(blank=True, default=dict)),
|
||||
('result_text', models.TextField(blank=True, default='')),
|
||||
('is_error', models.BooleanField(default=False)),
|
||||
('exit_code', models.IntegerField(blank=True, null=True)),
|
||||
('duration_ms', models.IntegerField(blank=True, null=True)),
|
||||
('seq', models.IntegerField(default=0)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tool_calls', to='openclaw.message')),
|
||||
('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tool_calls', to='openclaw.session')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'tool_calls',
|
||||
'ordering': ['seq'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
src/openclaw/migrations/__init__.py
Normal file
0
src/openclaw/migrations/__init__.py
Normal file
99
src/openclaw/models.py
Normal file
99
src/openclaw/models.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Session(models.Model):
|
||||
session_id = models.CharField(max_length=64)
|
||||
agent_name = models.CharField(max_length=128)
|
||||
source_node = models.CharField(max_length=64)
|
||||
session_version = models.IntegerField(default=0)
|
||||
model_provider = models.CharField(max_length=64, blank=True, default="")
|
||||
model_id = models.CharField(max_length=128, blank=True, default="")
|
||||
thinking_level = models.CharField(max_length=64, blank=True, default="")
|
||||
start_time = models.DateTimeField(null=True, blank=True)
|
||||
end_time = models.DateTimeField(null=True, blank=True)
|
||||
cwd = models.CharField(max_length=512, blank=True, default="")
|
||||
total_tokens = models.IntegerField(default=0)
|
||||
total_cost = models.FloatField(default=0.0)
|
||||
message_count = models.IntegerField(default=0)
|
||||
tool_call_count = models.IntegerField(default=0)
|
||||
error_count = models.IntegerField(default=0)
|
||||
raw_file_path = models.TextField(blank=True, default="")
|
||||
pushed_at = models.DateTimeField(null=True, blank=True)
|
||||
status = models.CharField(max_length=16, default="active")
|
||||
metadata = models.JSONField(default=dict, blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "sessions"
|
||||
unique_together = ("session_id", "agent_name")
|
||||
ordering = ["-start_time"]
|
||||
|
||||
def __str__(self):
|
||||
return f"Session({self.session_id} {self.agent_name})"
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
session = models.ForeignKey(
|
||||
Session, on_delete=models.CASCADE, related_name="messages"
|
||||
)
|
||||
message_id = models.CharField(max_length=128)
|
||||
parent_id = models.CharField(max_length=128, blank=True, default="")
|
||||
seq = models.IntegerField(default=0)
|
||||
role = models.CharField(max_length=32)
|
||||
content_text = models.TextField(blank=True, default="")
|
||||
raw_content = models.JSONField(default=list, blank=True)
|
||||
raw_message = models.JSONField(default=dict, blank=True)
|
||||
timestamp = models.DateTimeField()
|
||||
# assistant 专用
|
||||
model = models.CharField(max_length=128, blank=True, default="")
|
||||
provider = models.CharField(max_length=64, blank=True, default="")
|
||||
stop_reason = models.CharField(max_length=64, blank=True, default="")
|
||||
tokens_input = models.IntegerField(default=0)
|
||||
tokens_output = models.IntegerField(default=0)
|
||||
tokens_cache_read = models.IntegerField(default=0)
|
||||
tokens_cache_write = models.IntegerField(default=0)
|
||||
tokens_total = models.IntegerField(default=0)
|
||||
cost_total = models.FloatField(default=0.0)
|
||||
# toolResult 专用
|
||||
tool_call_id = models.CharField(max_length=128, blank=True, default="")
|
||||
tool_name = models.CharField(max_length=128, blank=True, default="")
|
||||
is_error = models.BooleanField(default=False)
|
||||
exit_code = models.IntegerField(null=True, blank=True)
|
||||
duration_ms = models.IntegerField(null=True, blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "messages"
|
||||
ordering = ["seq"]
|
||||
|
||||
def __str__(self):
|
||||
return f"Message({self.message_id} {self.role})"
|
||||
|
||||
|
||||
class ToolCall(models.Model):
|
||||
session = models.ForeignKey(
|
||||
Session, on_delete=models.CASCADE, related_name="tool_calls"
|
||||
)
|
||||
message = models.ForeignKey(
|
||||
Message, on_delete=models.CASCADE, related_name="tool_calls"
|
||||
)
|
||||
tool_call_id = models.CharField(max_length=128)
|
||||
tool_name = models.CharField(max_length=128)
|
||||
arguments = models.JSONField(default=dict, blank=True)
|
||||
result_text = models.TextField(blank=True, default="")
|
||||
is_error = models.BooleanField(default=False)
|
||||
exit_code = models.IntegerField(null=True, blank=True)
|
||||
duration_ms = models.IntegerField(null=True, blank=True)
|
||||
seq = models.IntegerField(default=0)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "tool_calls"
|
||||
ordering = ["seq"]
|
||||
|
||||
def __str__(self):
|
||||
return f"ToolCall({self.tool_name} {self.tool_call_id})"
|
||||
57
tests/test_models.py
Normal file
57
tests/test_models.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from openclaw.models import Session, Message, ToolCall
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestModelFields:
|
||||
def test_session_creation(self):
|
||||
s = Session.objects.create(
|
||||
session_id="a" * 36,
|
||||
agent_name="xingyao",
|
||||
source_node="macmini",
|
||||
status="active",
|
||||
)
|
||||
assert s.session_id == "a" * 36
|
||||
assert s.total_tokens == 0
|
||||
assert s.message_count == 0
|
||||
|
||||
def test_message_creation(self):
|
||||
s = Session.objects.create(
|
||||
session_id="b" * 36,
|
||||
agent_name="test",
|
||||
source_node="ubuntu1",
|
||||
status="active",
|
||||
)
|
||||
msg = Message.objects.create(
|
||||
session=s,
|
||||
message_id="msg-001",
|
||||
parent_id="root",
|
||||
role="assistant",
|
||||
timestamp=datetime(2026, 4, 5, 10, 0, tzinfo=timezone.utc),
|
||||
)
|
||||
assert msg.role == "assistant"
|
||||
assert msg.tokens_total == 0
|
||||
|
||||
def test_toolcall_creation(self):
|
||||
s = Session.objects.create(
|
||||
session_id="c" * 36,
|
||||
agent_name="test",
|
||||
source_node="ubuntu2",
|
||||
status="active",
|
||||
)
|
||||
msg = Message.objects.create(
|
||||
session=s,
|
||||
message_id="msg-002",
|
||||
parent_id="root",
|
||||
role="assistant",
|
||||
timestamp=datetime(2026, 4, 5, 10, 0, tzinfo=timezone.utc),
|
||||
)
|
||||
tc = ToolCall.objects.create(
|
||||
session=s,
|
||||
message=msg,
|
||||
tool_call_id="call_0",
|
||||
tool_name="exec",
|
||||
)
|
||||
assert tc.tool_name == "exec"
|
||||
assert tc.is_error is False
|
||||
Reference in New Issue
Block a user