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