-
Notifications
You must be signed in to change notification settings - Fork 0
Add conversations/messages models for running capabilities #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3de047c
fd154fd
2df2715
bef2fb5
d844fcd
0f9969b
b2f0aa4
f4ad8dd
7a0db41
e97c56c
5c4b8f7
ed5faad
279bc30
1ac0680
3868071
72861c7
1e9c57e
89b4d12
607f0fe
541de16
9a6fc1a
ec5885a
4e893f4
4041da2
54e3da5
44d9db2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1466,6 +1466,46 @@ components: | |
| - username | ||
| - value | ||
| type: object | ||
| conversation: | ||
| description: Represents a conversation between a user and AI assistant with | ||
| running capabilities. | ||
| properties: | ||
| BaseModel: | ||
| type: object | ||
| created: | ||
| description: Timestamp when the conversation was created (RFC3339). | ||
| example: "2023-10-27T10:00:00Z" | ||
| type: string | ||
| key: | ||
| description: Unique key for the conversation. | ||
| example: '#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000' | ||
| type: string | ||
| name: | ||
| description: Name of the conversation. | ||
| example: My AI Assistant Chat | ||
| type: string | ||
| ttl: | ||
| description: Time-to-live for the conversation record (Unix timestamp). | ||
| example: "1706353200" | ||
| format: int64 | ||
| type: integer | ||
| username: | ||
| description: Username who owns the conversation. | ||
| example: [email protected] | ||
| type: string | ||
| uuid: | ||
| description: UUID of the conversation for reference. | ||
| example: 550e8400-e29b-41d4-a716-446655440000 | ||
| type: string | ||
| required: | ||
| - BaseModel | ||
| - created | ||
| - key | ||
| - name | ||
| - ttl | ||
| - username | ||
| - uuid | ||
| type: object | ||
| cpe: | ||
| description: Represents a Common Platform Enumeration (CPE) identifier, used | ||
| for naming hardware, software, and operating systems. | ||
|
|
@@ -2094,6 +2134,10 @@ components: | |
| description: Configuration parameters for the job capability. | ||
| example: '{"test": "cve-1111-2222"}' | ||
| type: object | ||
| conversation: | ||
| description: UUID of the conversation that initiated this job. | ||
| example: 550e8400-e29b-41d4-a716-446655440000 | ||
| type: string | ||
| created: | ||
| description: Timestamp when the job was created (RFC3339). | ||
| example: "2023-10-27T10:00:00Z" | ||
|
|
@@ -2230,6 +2274,56 @@ components: | |
| - username | ||
| - visited | ||
| type: object | ||
| message: | ||
| description: Represents a message within a conversation, with KSUID ordering | ||
| for proper sequencing. | ||
| properties: | ||
| BaseModel: | ||
| type: object | ||
| content: | ||
| description: Content of the message. | ||
| example: Hello, how can I help you today? | ||
| type: string | ||
| conversationId: | ||
| description: ID of the conversation this message belongs to. | ||
| example: 550e8400-e29b-41d4-a716-446655440000 | ||
| type: string | ||
| key: | ||
| description: Unique key for the message. | ||
| example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK' | ||
| type: string | ||
| messageId: | ||
| description: KSUID for message ordering. | ||
| example: 1sB5tZfLipTVWQWHVKnDFS6kFRK | ||
| type: string | ||
| role: | ||
| description: Role of the message sender (user, chariot, system). | ||
| example: user | ||
| type: string | ||
| timestamp: | ||
| description: Timestamp when the message was created (RFC3339). | ||
| example: "2023-10-27T10:00:00Z" | ||
| type: string | ||
| ttl: | ||
| description: Time-to-live for the message record (Unix timestamp). | ||
| example: "1706353200" | ||
| format: int64 | ||
| type: integer | ||
| username: | ||
| description: Username who sent the message. | ||
| example: [email protected] | ||
| type: string | ||
| required: | ||
| - BaseModel | ||
| - content | ||
| - conversationId | ||
| - key | ||
| - messageId | ||
| - role | ||
| - timestamp | ||
| - ttl | ||
| - username | ||
| type: object | ||
| metadata: | ||
| description: Contains metadata about an asset, including discovery information, | ||
| relationships, and attributes. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package model | ||
|
|
||
| import ( | ||
| "github.com/google/uuid" | ||
| "github.com/praetorian-inc/tabularium/pkg/registry" | ||
| ) | ||
|
|
||
| type Conversation struct { | ||
| registry.BaseModel | ||
| baseTableModel | ||
| Username string `dynamodbav:"username" json:"username" desc:"Username who owns the conversation." example:"[email protected]"` | ||
| Key string `dynamodbav:"key" json:"key" desc:"Unique key for the conversation." example:"#conversation#example-conversation#550e8400-e29b-41d4-a716-446655440000"` | ||
| UUID string `dynamodbav:"uuid" json:"uuid" desc:"UUID of the conversation for reference." example:"550e8400-e29b-41d4-a716-446655440000"` | ||
| User string `dynamodbav:"user" json:"user" desc:"Who started the conversation." example:"[email protected]"` | ||
| Created string `dynamodbav:"created" json:"created" desc:"Timestamp when the conversation was created (RFC3339)." example:"2023-10-27T10:00:00Z"` | ||
| Topic string `dynamodbav:"topic" json:"topic" desc:"Topic extracted from first message (first 256 chars)." example:"Find all active assets in my infrastructure"` | ||
| } | ||
|
|
||
| func init() { | ||
| registry.Registry.MustRegisterModel(&Conversation{}) | ||
| } | ||
|
|
||
| func (c *Conversation) GetKey() string { | ||
| return c.Key | ||
| } | ||
|
|
||
| func (c *Conversation) GetDescription() string { | ||
| return "Represents a conversation between a user and AI assistant with running capabilities." | ||
| } | ||
|
|
||
| func (c *Conversation) Defaulted() { | ||
| c.Created = Now() | ||
| } | ||
|
|
||
| func (c *Conversation) GetHooks() []registry.Hook { | ||
| return []registry.Hook{ | ||
| { | ||
| Call: func() error { | ||
| if c.Key == "" { | ||
| conversationID := uuid.New().String() | ||
| c.UUID = conversationID | ||
| c.Key = "#conversation#" + conversationID | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return nil | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func (c *Conversation) Valid() bool { | ||
| return c.Key != "" | ||
| } | ||
|
|
||
| func NewConversation(topic string) Conversation { | ||
| conv := Conversation{ | ||
| Topic: topic, | ||
| } | ||
| conv.Defaulted() | ||
| registry.CallHooks(&conv) | ||
| return conv | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,187 @@ | ||||||||||||||
| package model | ||||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "strings" | ||||||||||||||
| "testing" | ||||||||||||||
|
|
||||||||||||||
| "github.com/praetorian-inc/tabularium/pkg/registry" | ||||||||||||||
| "github.com/stretchr/testify/assert" | ||||||||||||||
| "github.com/stretchr/testify/require" | ||||||||||||||
| ) | ||||||||||||||
|
Comment on lines
+7
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Tests must use the standard library (no testify). Project guideline: “**/*_test.go: Use the standard Go testing framework; do not introduce additional test frameworks.” Replace assert/require with native checks (if/tt.Errorf/tt.Fatal). Example refactor pattern:
Remove these imports: - "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| func TestConversation_NewConversation(t *testing.T) { | ||||||||||||||
| topic := "Test Conversation" | ||||||||||||||
|
|
||||||||||||||
| conv := NewConversation(topic) | ||||||||||||||
|
|
||||||||||||||
| assert.Equal(t, topic, conv.Topic) | ||||||||||||||
| assert.NotEmpty(t, conv.UUID) | ||||||||||||||
| assert.NotEmpty(t, conv.Created) | ||||||||||||||
| assert.NotEmpty(t, conv.Source) | ||||||||||||||
| assert.NotEmpty(t, conv.Key) | ||||||||||||||
| assert.True(t, strings.HasPrefix(conv.Key, "#conversation#")) | ||||||||||||||
| assert.True(t, conv.Valid()) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_GetKey(t *testing.T) { | ||||||||||||||
| conv := NewConversation("test") | ||||||||||||||
| assert.Equal(t, conv.Key, conv.GetKey()) | ||||||||||||||
| assert.NotEmpty(t, conv.GetKey()) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_GetDescription(t *testing.T) { | ||||||||||||||
| conv := &Conversation{} | ||||||||||||||
| expected := "Represents a conversation between a user and AI assistant with running capabilities." | ||||||||||||||
| assert.Equal(t, expected, conv.GetDescription()) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_Defaulted(t *testing.T) { | ||||||||||||||
| conv := &Conversation{} | ||||||||||||||
| conv.Defaulted() | ||||||||||||||
|
|
||||||||||||||
| assert.NotEmpty(t, conv.Created) | ||||||||||||||
| assert.NotEmpty(t, conv.Source) | ||||||||||||||
|
|
||||||||||||||
| // Verify TTL is approximately 30 days from now | ||||||||||||||
| assert.NotEmpty(t, conv.Created) // Allow 60 seconds tolerance | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_Hooks(t *testing.T) { | ||||||||||||||
| conv := &Conversation{} | ||||||||||||||
|
|
||||||||||||||
| // Call hooks manually | ||||||||||||||
| registry.CallHooks(conv) | ||||||||||||||
|
|
||||||||||||||
| assert.NotEmpty(t, conv.Key) | ||||||||||||||
| assert.True(t, strings.HasPrefix(conv.Key, "#conversation#")) | ||||||||||||||
|
|
||||||||||||||
| // Verify UUID format in key (should be 36 characters with dashes) | ||||||||||||||
| keyParts := strings.Split(conv.Key, "#") | ||||||||||||||
| require.Len(t, keyParts, 3) | ||||||||||||||
| uuid := keyParts[2] | ||||||||||||||
| assert.Len(t, uuid, 36) | ||||||||||||||
| assert.Contains(t, uuid, "-") | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_Hooks_ExistingKey(t *testing.T) { | ||||||||||||||
| existingKey := "#conversation#existing#12345" | ||||||||||||||
| conv := &Conversation{ | ||||||||||||||
| Key: existingKey, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| registry.CallHooks(conv) | ||||||||||||||
|
|
||||||||||||||
| // Should not change existing key | ||||||||||||||
| assert.Equal(t, existingKey, conv.Key) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_RegistryIntegration(t *testing.T) { | ||||||||||||||
| // Test that the conversation is properly registered in the registry | ||||||||||||||
| conv := &Conversation{} | ||||||||||||||
|
|
||||||||||||||
| // Check that it's registered by calling a registry function | ||||||||||||||
| hooks := conv.GetHooks() | ||||||||||||||
| assert.Len(t, hooks, 1) | ||||||||||||||
|
|
||||||||||||||
| // Verify hook functionality | ||||||||||||||
| err := hooks[0].Call() | ||||||||||||||
| assert.NoError(t, err) | ||||||||||||||
| assert.NotEmpty(t, conv.Key) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_KeyGeneration_Uniqueness(t *testing.T) { | ||||||||||||||
| // Test that multiple conversations with same topic get different keys | ||||||||||||||
| topic := "Same Name" | ||||||||||||||
|
|
||||||||||||||
| conv1 := NewConversation(topic) | ||||||||||||||
| conv2 := NewConversation(topic) | ||||||||||||||
|
|
||||||||||||||
| assert.NotEqual(t, conv1.Key, conv2.Key) | ||||||||||||||
| assert.True(t, strings.HasPrefix(conv1.Key, "#conversation#")) | ||||||||||||||
| assert.True(t, strings.HasPrefix(conv2.Key, "#conversation#")) | ||||||||||||||
| assert.Equal(t, topic, conv1.Topic) | ||||||||||||||
| assert.Equal(t, topic, conv2.Topic) | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_SecurityScenarios(t *testing.T) { | ||||||||||||||
| testCases := []struct { | ||||||||||||||
| name string | ||||||||||||||
| conversationName string | ||||||||||||||
| expectValid bool | ||||||||||||||
| }{ | ||||||||||||||
| { | ||||||||||||||
| name: "valid standard conversation", | ||||||||||||||
| conversationName: "Normal Conversation", | ||||||||||||||
| expectValid: true, | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| name: "conversation with special characters", | ||||||||||||||
| conversationName: "Conv/\\with<>special|chars", | ||||||||||||||
| expectValid: true, | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| name: "very long conversation name", | ||||||||||||||
| conversationName: strings.Repeat("a", 1000), | ||||||||||||||
| expectValid: true, | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| name: "SQL injection attempt in name", | ||||||||||||||
| conversationName: "'; DROP TABLE users; --", | ||||||||||||||
| expectValid: true, // Should be treated as regular string | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| name: "XSS attempt in name", | ||||||||||||||
| conversationName: "<script>alert('xss')</script>", | ||||||||||||||
| expectValid: true, // Should be treated as regular string | ||||||||||||||
| }, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| for _, tc := range testCases { | ||||||||||||||
| t.Run(tc.name, func(t *testing.T) { | ||||||||||||||
| conv := NewConversation(tc.conversationName) | ||||||||||||||
|
|
||||||||||||||
| assert.Equal(t, tc.expectValid, conv.Valid()) | ||||||||||||||
| if tc.expectValid { | ||||||||||||||
| assert.Equal(t, tc.conversationName, conv.Topic) | ||||||||||||||
| assert.NotEmpty(t, conv.Key) | ||||||||||||||
| } | ||||||||||||||
| }) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func TestConversation_TopicField(t *testing.T) { | ||||||||||||||
| testCases := []struct { | ||||||||||||||
| name string | ||||||||||||||
| topic string | ||||||||||||||
| expected string | ||||||||||||||
| }{ | ||||||||||||||
| { | ||||||||||||||
| name: "short topic", | ||||||||||||||
| topic: "Find all assets", | ||||||||||||||
| expected: "Find all assets", | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| name: "long topic gets truncated", | ||||||||||||||
| topic: strings.Repeat("a", 300), | ||||||||||||||
| expected: strings.Repeat("a", 256), | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| name: "empty topic", | ||||||||||||||
| topic: "", | ||||||||||||||
| expected: "", | ||||||||||||||
| }, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| for _, tc := range testCases { | ||||||||||||||
| t.Run(tc.name, func(t *testing.T) { | ||||||||||||||
| conv := NewConversation("Test Chat") | ||||||||||||||
| conv.Topic = tc.topic | ||||||||||||||
|
|
||||||||||||||
| if len(tc.topic) > 256 { | ||||||||||||||
| conv.Topic = tc.topic[:256] | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| assert.Equal(t, tc.expected, conv.Topic) | ||||||||||||||
| }) | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,8 @@ type Job struct { | |
| Full bool `dynamodbav:"-" json:"full,omitempty" desc:"Indicates if this is a full scan job." example:"false"` | ||
| Capabilities []string `dynamodbav:"-" json:"capabilities,omitempty" desc:"List of specific capabilities to run for this job." example:"[\"portscan\", \"nuclei\"]"` | ||
| Queue string `dynamodbav:"-" desc:"Target queue for the job." example:"standard"` | ||
| Conversation string `dynamodbav:"conversation,omitempty" json:"conversation,omitempty" desc:"UUID of the conversation that initiated this job." example:"550e8400-e29b-41d4-a716-446655440000"` | ||
| User string `dynamodbav:"user,omitempty" json:"user,omitempty" desc:"User who initiated this job." example:"[email protected]"` | ||
| Origin TargetWrapper `dynamodbav:"origin" json:"origin" desc:"The job that originally started this chain of jobs."` | ||
| Target TargetWrapper `dynamodbav:"target" json:"target" desc:"The primary target of the job."` | ||
| Parent TargetWrapper `dynamodbav:"parent" json:"parent,omitempty" desc:"Optional parent target from which this job was spawned."` | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align message.role description and include tool fields.
Code defines additional roles and tool-use fields; OpenAPI should reflect them.
Apply:
message: description: Represents a message within a conversation, with KSUID ordering for proper sequencing. properties: BaseModel: type: object content: description: Content of the message. example: Hello, how can I help you today? type: string conversationId: description: ID of the conversation this message belongs to. example: 550e8400-e29b-41d4-a716-446655440000 type: string key: description: Unique key for the message. example: '#message#550e8400-e29b-41d4-a716-446655440000#1sB5tZfLipTVWQWHVKnDFS6kFRK' type: string messageId: description: KSUID for message ordering. example: 1sB5tZfLipTVWQWHVKnDFS6kFRK type: string role: - description: Role of the message sender (user, chariot, system). + description: Role of the message sender (user, chariot, system, tool call, tool response, planner-output). example: user type: string timestamp: description: Timestamp when the message was created (RFC3339). example: "2023-10-27T10:00:00Z" type: string ttl: description: Time-to-live for the message record (Unix timestamp). example: "1706353200" format: int64 type: integer username: description: Username who sent the message. example: [email protected] type: string + toolUseId: + description: Tool use ID for tool result messages. + example: tooluse_kZJMlvQmRJ6eAyJE5GIl7Q + type: string + toolUseContent: + description: JSON serialized tool use content for assistant tool use messages. + example: '{"name":"query","input":{"node":{"labels":["Asset"]}}}' + type: string required: - BaseModel - content - conversationId - key - messageId - role - timestamp - ttl - username + # toolUseId/toolUseContent are optional type: object📝 Committable suggestion