Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5855a4c
Build response code structure
locnguyen1986 Sep 4, 2025
b2e6a03
extract handler to different provider and fix API dos
locnguyen1986 Sep 5, 2025
fb52a73
add the new OpenAI Response object, update swagger
locnguyen1986 Sep 5, 2025
0816fb8
Merge pull request #90 from menloresearch/feat/73/response-conversati…
locnguyen1986 Sep 5, 2025
fe81bbc
Merge branch 'main' into epic/92/openai-compatible-response-api
locnguyen1986 Sep 8, 2025
2407424
Merge branch 'main' into epic/92/openai-compatible-response-api
locnguyen1986 Sep 8, 2025
1af54a5
Merge branch 'main' into epic/92/openai-compatible-response-api
locnguyen1986 Sep 8, 2025
cee84c3
add new middleware for api key user
locnguyen1986 Sep 8, 2025
4e20a93
Merge branch 'feat/73/conversation-handler-apikey' into epic/92/opena…
locnguyen1986 Sep 8, 2025
f9d76d7
add non-stream processing
locnguyen1986 Sep 8, 2025
3ef813f
add conversation creation
locnguyen1986 Sep 8, 2025
f2335b2
remove adhoc struct by using response entity
locnguyen1986 Sep 9, 2025
0222ba7
fix abort issue, replace interface by any
locnguyen1986 Sep 9, 2025
0a224c5
Merge pull request #106 from menloresearch/feat/94/add-non-stream-com…
locnguyen1986 Sep 9, 2025
abf4580
Merge branch 'main' into epic/92/openai-compatible-response-api
locnguyen1986 Sep 9, 2025
d8fa6dd
support response streaming
locnguyen1986 Sep 10, 2025
c6c9f49
improve streaming handler
locnguyen1986 Sep 10, 2025
1db4db8
save response with previous response context
locnguyen1986 Sep 10, 2025
34329fc
stateful responses
locnguyen1986 Sep 10, 2025
63c707e
PR feedbacks
locnguyen1986 Sep 11, 2025
092c9ce
add streaming content
locnguyen1986 Sep 12, 2025
45391ec
add reasoning content buffer
locnguyen1986 Sep 12, 2025
360497d
allow store=false which don't save the conversation
locnguyen1986 Sep 12, 2025
7cff2fc
move to /v1
locnguyen1986 Sep 12, 2025
d343537
Merge pull request #114 from menloresearch/feat/95/add-streaming-resp…
locnguyen1986 Sep 12, 2025
81fd36a
Merge branch 'main' into epic/92/openai-compatible-response-api
locnguyen1986 Sep 12, 2025
5860a84
fix errors
locnguyen1986 Sep 12, 2025
5e142ce
move all responses endpoint to /v1, fix middleware
locnguyen1986 Sep 15, 2025
6f90418
move middleware get to service
locnguyen1986 Sep 15, 2025
1a0a44c
add input item filters
locnguyen1986 Sep 15, 2025
a15ddf8
move responses handler into responses service
locnguyen1986 Sep 15, 2025
54027f9
fix the model, follow clean architect
locnguyen1986 Sep 15, 2025
726693d
remove unused function in conversation
locnguyen1986 Sep 15, 2025
b58dd80
fix the models
locnguyen1986 Sep 15, 2025
e8361cf
improve error handling
locnguyen1986 Sep 15, 2025
b1d1c6c
remove api key and check error
locnguyen1986 Sep 15, 2025
9b0e7d2
refactor logic
locnguyen1986 Sep 15, 2025
3e0afe2
improve error handling
locnguyen1986 Sep 15, 2025
264e48e
fix the common error and remove unused doc files
locnguyen1986 Sep 15, 2025
df7d6a2
Merge branch 'main' into epic/92/openai-compatible-response-api
locnguyen1986 Sep 15, 2025
b1ca929
restructure completion streaming
locnguyen1986 Sep 16, 2025
e1dd69d
refactor directory
locnguyen1986 Sep 16, 2025
5bf69f6
fix errors and repository
locnguyen1986 Sep 16, 2025
a77318c
PR feedbacks on error handling
locnguyen1986 Sep 16, 2025
77defff
fix function namings
locnguyen1986 Sep 16, 2025
881b5e1
wrapper params
locnguyen1986 Sep 16, 2025
99a45d9
restructure response creation flow
locnguyen1986 Sep 16, 2025
6d7f95b
fix model and factory items
locnguyen1986 Sep 16, 2025
ee4c656
remove OpenAIGeneralResponse as it's overengineer now
locnguyen1986 Sep 16, 2025
ef37e00
remove unreach code
locnguyen1986 Sep 16, 2025
6937d3f
remove unreach code
locnguyen1986 Sep 16, 2025
422e420
Merge branch 'epic/92/openai-compatible-response-api' into epic/68/co…
locnguyen1986 Sep 16, 2025
7d2aec0
refactor code by routing handler
locnguyen1986 Sep 16, 2025
4311b9b
fix patter
locnguyen1986 Sep 16, 2025
a04940d
fix the conversation routes with checking
locnguyen1986 Sep 16, 2025
d1309c2
resolve conflicts
locnguyen1986 Sep 16, 2025
0d56859
fix latest message
locnguyen1986 Sep 16, 2025
6e9be6c
Merge pull request #134 from menloresearch/epic/68/conversation-revisit
locnguyen1986 Sep 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (s *AuthService) AppUserAuthMiddleware() gin.HandlerFunc {
}

reqCtx.AbortWithStatusJSON(http.StatusUnauthorized, responses.ErrorResponse{
Code: "4026757e-d5a4-4cf7-8914-2c96f011084f",
Code: "019947f0-eca1-7474-8ed2-09d6e5389b54",
})
}
}
Expand Down
56 changes: 56 additions & 0 deletions apps/jan-api-gateway/application/app/domain/common/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package common

import "fmt"

// Error represents a standardized error with code and underlying error
type Error struct {
Err error `json:"-"`
Code string `json:"code"`
}

// NewError creates a new Error instance from an existing error
func NewError(err error, code string) *Error {
return &Error{
Err: err,
Code: code,
}
}

// NewErrorWithMessage creates a new Error instance with a custom message
func NewErrorWithMessage(message string, code string) *Error {
return &Error{
Err: fmt.Errorf("%s", message),
Code: code,
}
}

// Error implements the error interface
func (e *Error) Error() string {
if e.Err != nil {
return e.Err.Error()
}
return ""
}

// String returns the string representation of the error
func (e *Error) String() string {
return e.Error()
}

// GetMessage returns the error message from the underlying error
func (e *Error) GetMessage() string {
if e.Err != nil {
return e.Err.Error()
}
return ""
}

// GetCode returns the error code
func (e *Error) GetCode() string {
return e.Code
}

// GetCode returns the error code
func (e *Error) GetError() error {
return e.Err
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package conversation

import (
"context"
"time"

"menlo.ai/jan-api-gateway/app/domain/query"
)
Expand Down Expand Up @@ -50,18 +51,53 @@ func ValidateItemRole(input string) bool {
}
}

// @Enum(pending, in_progress, completed, failed, cancelled)
type ItemStatus string

const (
ItemStatusPending ItemStatus = "pending"
ItemStatusInProgress ItemStatus = "in_progress"
ItemStatusCompleted ItemStatus = "completed"
ItemStatusFailed ItemStatus = "failed"
ItemStatusCancelled ItemStatus = "cancelled"
)

func ValidateItemStatus(input string) bool {
switch ItemStatus(input) {
case ItemStatusPending, ItemStatusInProgress, ItemStatusCompleted, ItemStatusFailed, ItemStatusCancelled:
return true
default:
return false
}
}

// ToItemStatusPtr returns a pointer to the given ItemStatus
func ToItemStatusPtr(s ItemStatus) *ItemStatus {
return &s
}

// ItemStatusToStringPtr converts *ItemStatus to *string
func ItemStatusToStringPtr(s *ItemStatus) *string {
if s == nil {
return nil
}
str := string(*s)
return &str
}

type Item struct {
ID uint `json:"-"` // Internal DB ID (hidden from JSON)
ID uint `json:"-"`
ConversationID uint `json:"-"`
PublicID string `json:"id"` // OpenAI-compatible string ID like "msg_abc123"
PublicID string `json:"id"`
Type ItemType `json:"type"`
Role *ItemRole `json:"role,omitempty"`
Content []Content `json:"content,omitempty"`
Status *string `json:"status,omitempty"`
IncompleteAt *int64 `json:"incomplete_at,omitempty"`
Status *ItemStatus `json:"status,omitempty"`
IncompleteAt *time.Time `json:"incomplete_at,omitempty"`
IncompleteDetails *IncompleteDetails `json:"incomplete_details,omitempty"`
CompletedAt *int64 `json:"completed_at,omitempty"`
CreatedAt int64 `json:"created_at"` // Unix timestamp for OpenAI compatibility
CompletedAt *time.Time `json:"completed_at,omitempty"`
ResponseID *uint `json:"-"`
CreatedAt time.Time `json:"created_at"`
}

type Content struct {
Expand Down Expand Up @@ -129,16 +165,16 @@ type IncompleteDetails struct {
}

type Conversation struct {
ID uint `json:"-"` // Internal DB ID (hidden from JSON)
ID uint `json:"-"`
PublicID string `json:"id"` // OpenAI-compatible string ID like "conv_abc123"
Title *string `json:"title,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we have to extend it for the chat apps

UserID uint `json:"-"` // Internal user ID (hidden from JSON)
UserID uint `json:"-"`
Status ConversationStatus `json:"status"`
Items []Item `json:"items,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
IsPrivate bool `json:"is_private"`
CreatedAt int64 `json:"created_at"` // Unix timestamp for OpenAI compatibility
UpdatedAt int64 `json:"updated_at"` // Unix timestamp for OpenAI compatibility
CreatedAt time.Time `json:"created_at"` // Unix timestamp for OpenAI compatibility
UpdatedAt time.Time `json:"updated_at"` // Unix timestamp for OpenAI compatibility
}

type ConversationFilter struct {
Expand All @@ -149,6 +185,8 @@ type ConversationFilter struct {
type ItemFilter struct {
PublicID *string
ConversationID *uint
Role *ItemRole
ResponseID *uint
}

type ConversationRepository interface {
Expand Down Expand Up @@ -177,3 +215,26 @@ type ItemRepository interface {
FindByFilter(ctx context.Context, filter ItemFilter, pagination *query.Pagination) ([]*Item, error)
Count(ctx context.Context, filter ItemFilter) (int64, error)
}

// NewItem creates a new conversation item with the given parameters
func NewItem(publicID string, itemType ItemType, role ItemRole, content []Content, conversationID uint, responseID *uint) *Item {
return &Item{
PublicID: publicID,
Type: itemType,
Role: &role,
Content: content,
ConversationID: conversationID,
ResponseID: responseID,
CreatedAt: time.Now(),
}
}

// NewTextContent creates a new text content item
func NewTextContent(text string) Content {
return Content{
Type: "text",
Text: &Text{
Value: text,
},
}
}
Loading
Loading