feat: add Session, Message, ToolCall models with migrations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 14:51:29 +08:00
parent f115a48c59
commit efc8c474fc
4 changed files with 258 additions and 0 deletions

View 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'],
},
),
]

View File

99
src/openclaw/models.py Normal file
View 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
View 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