Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 541e066

Browse files
authored
Merge pull request #670 from janhq/feat/add-message-apis
feat: add update message api
2 parents bfa875a + 6c28a2b commit 541e066

File tree

11 files changed

+232
-32
lines changed

11 files changed

+232
-32
lines changed

cortex-js/src/infrastructure/commanders/usecases/chat.cli.usecases.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { ChatUsecases } from '@/usecases/chat/chat.usecases';
21
import {
32
ChatCompletionRole,
43
ContentType,
@@ -8,7 +7,6 @@ import { exit, stdin, stdout } from 'node:process';
87
import * as readline from 'node:readline/promises';
98
import { ChatCompletionMessage } from '@/infrastructure/dtos/chat/chat-completion-message.dto';
109
import { CreateChatCompletionDto } from '@/infrastructure/dtos/chat/create-chat-completion.dto';
11-
import { CortexUsecases } from '@/usecases/cortex/cortex.usecases';
1210
import { Injectable } from '@nestjs/common';
1311
import { ThreadsUsecases } from '@/usecases/threads/threads.usecases';
1412
import { Thread } from '@/domain/models/thread.interface';
@@ -20,6 +18,7 @@ import stream from 'stream';
2018
import { CreateMessageDto } from '@/infrastructure/dtos/messages/create-message.dto';
2119
import { MessagesUsecases } from '@/usecases/messages/messages.usecases';
2220
import { ModelParameterParser } from '../utils/model-parameter.parser';
21+
import { ChatUsecases } from '@/usecases/chat/chat.usecases';
2322

2423
@Injectable()
2524
export class ChatCliUsecases {
@@ -31,7 +30,6 @@ export class ChatCliUsecases {
3130
private readonly assistantUsecases: AssistantsUsecases,
3231
private readonly threadUsecases: ThreadsUsecases,
3332
private readonly chatUsecases: ChatUsecases,
34-
private readonly cortexUsecases: CortexUsecases,
3533
private readonly modelsUsecases: ModelsUsecases,
3634
private readonly messagesUsecases: MessagesUsecases,
3735
) {}
@@ -268,7 +266,6 @@ export class ChatCliUsecases {
268266
};
269267

270268
const createThreadDto: CreateThreadDto = {
271-
title: 'New Thread',
272269
assistants: [assistantDto],
273270
};
274271

cortex-js/src/infrastructure/controllers/assistants.controller.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {
22
Body,
33
Controller,
4+
DefaultValuePipe,
45
Delete,
56
Get,
67
Param,
78
Post,
9+
Query,
810
UseInterceptors,
911
} from '@nestjs/common';
1012
import { AssistantsUsecases } from '@/usecases/assistants/assistants.usecases';
@@ -17,6 +19,7 @@ import {
1719
ApiParam,
1820
ApiTags,
1921
ApiResponse,
22+
ApiQuery,
2023
} from '@nestjs/swagger';
2124
import { AssistantEntity } from '../entities/assistant.entity';
2225
import { TransformInterceptor } from '../interceptors/transform.interceptor';
@@ -41,16 +44,48 @@ export class AssistantsController {
4144

4245
@ApiOperation({
4346
summary: 'List assistants',
44-
description:
45-
'Retrieves all the available assistants along with their settings.',
47+
description: 'Returns a list of assistants.',
4648
})
4749
@ApiOkResponse({
4850
description: 'Ok',
4951
type: [AssistantEntity],
5052
})
53+
@ApiQuery({
54+
name: 'limit',
55+
type: Number,
56+
required: false,
57+
description:
58+
'A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20.',
59+
})
60+
@ApiQuery({
61+
name: 'order',
62+
type: String,
63+
required: false,
64+
description:
65+
'Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order.',
66+
})
67+
@ApiQuery({
68+
name: 'after',
69+
type: String,
70+
required: false,
71+
description:
72+
'A cursor for use in pagination. after is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.',
73+
})
74+
@ApiQuery({
75+
name: 'before',
76+
type: String,
77+
required: false,
78+
description:
79+
'A cursor for use in pagination. before is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.',
80+
})
5181
@Get()
52-
findAll() {
53-
return this.assistantsService.findAll();
82+
findAll(
83+
@Query('limit', new DefaultValuePipe(20)) limit: number,
84+
@Query('order', new DefaultValuePipe('desc')) order: 'asc' | 'desc',
85+
@Query('after') after?: string,
86+
@Query('before') before?: string,
87+
) {
88+
return this.assistantsService.listAssistants(limit, order, after, before);
5489
}
5590

5691
@ApiOperation({

cortex-js/src/infrastructure/controllers/threads.controller.ts

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ import {
2525
} from '@nestjs/swagger';
2626
import { TransformInterceptor } from '../interceptors/transform.interceptor';
2727
import { ListMessagesResponseDto } from '../dtos/messages/list-message.dto';
28+
import { CreateMessageDto } from '../dtos/threads/create-message.dto';
29+
import { UpdateMessageDto } from '../dtos/threads/update-message.dto';
2830

2931
@ApiTags('Threads')
3032
@Controller('threads')
3133
@UseInterceptors(TransformInterceptor)
3234
export class ThreadsController {
33-
constructor(private readonly threadsService: ThreadsUsecases) {}
35+
constructor(private readonly threadsUsecases: ThreadsUsecases) {}
3436

3537
@ApiOperation({
3638
summary: 'Create thread',
3739
description: 'Creates a new thread.',
3840
})
3941
@Post()
4042
create(@Body() createThreadDto: CreateThreadDto) {
41-
return this.threadsService.create(createThreadDto);
43+
return this.threadsUsecases.create(createThreadDto);
4244
}
4345

4446
@ApiOperation({
@@ -48,7 +50,7 @@ export class ThreadsController {
4850
})
4951
@Get()
5052
findAll() {
51-
return this.threadsService.findAll();
53+
return this.threadsUsecases.findAll();
5254
}
5355

5456
@HttpCode(200)
@@ -109,7 +111,7 @@ export class ThreadsController {
109111
@Query('before') before?: string,
110112
@Query('run_id') runId?: string,
111113
) {
112-
return this.threadsService.getMessagesOfThread(
114+
return this.threadsUsecases.getMessagesOfThread(
113115
id,
114116
limit,
115117
order,
@@ -119,6 +121,58 @@ export class ThreadsController {
119121
);
120122
}
121123

124+
@ApiOperation({
125+
summary: 'Modifies a message.',
126+
description: 'Modifies a message.',
127+
})
128+
@ApiResponse({
129+
status: 201,
130+
description: 'A message object.',
131+
})
132+
@ApiOperation({
133+
summary: 'Create a message',
134+
description: 'Create a message.',
135+
})
136+
@ApiParam({
137+
name: 'id',
138+
required: true,
139+
description: 'The ID of the thread to create a message for.',
140+
})
141+
@Post(':id/messages')
142+
createMessageInThread(
143+
@Param('id') id: string,
144+
@Body() createMessageDto: CreateMessageDto,
145+
) {
146+
return this.threadsUsecases.createMessageInThread(id, createMessageDto);
147+
}
148+
149+
@ApiResponse({
150+
status: 200,
151+
description: 'The modified message object.',
152+
})
153+
@ApiParam({
154+
name: 'thread_id',
155+
required: true,
156+
description: 'The ID of the thread to which this message belongs.',
157+
})
158+
@ApiParam({
159+
name: 'message_id',
160+
required: true,
161+
description: 'The ID of the message to modify.',
162+
})
163+
@Post(':thread_id/messages/:message_id')
164+
updateMessage(
165+
@Param('thread_id') threadId: string,
166+
@Param('message_id') messageId: string,
167+
@Body() updateMessageDto: UpdateMessageDto,
168+
) {
169+
return this.threadsUsecases.updateMessage(
170+
threadId,
171+
messageId,
172+
updateMessageDto,
173+
);
174+
}
175+
122176
@ApiResponse({
123177
status: 200,
124178
description: 'Ok',
@@ -135,7 +189,7 @@ export class ThreadsController {
135189
})
136190
@Get(':id')
137191
findOne(@Param('id') id: string) {
138-
return this.threadsService.findOne(id);
192+
return this.threadsUsecases.findOne(id);
139193
}
140194

141195
@ApiResponse({
@@ -154,7 +208,7 @@ export class ThreadsController {
154208
})
155209
@Patch(':id')
156210
update(@Param('id') id: string, @Body() updateThreadDto: UpdateThreadDto) {
157-
return this.threadsService.update(id, updateThreadDto);
211+
return this.threadsUsecases.update(id, updateThreadDto);
158212
}
159213

160214
@ApiResponse({
@@ -173,6 +227,6 @@ export class ThreadsController {
173227
})
174228
@Delete(':id')
175229
remove(@Param('id') id: string) {
176-
return this.threadsService.remove(id);
230+
return this.threadsUsecases.remove(id);
177231
}
178232
}

cortex-js/src/infrastructure/dtos/assistants/assistant-tool.dto.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export class AssistantToolDto implements AssistantTool {
1515
@IsBoolean()
1616
enabled: boolean;
1717

18-
// TODO: NamH make a type for this
1918
@ApiProperty({
2019
description: "The setting of the assistant's tool.",
2120
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ChatCompletionRole } from '@/domain/models/message.interface';
2+
import { ApiProperty } from '@nestjs/swagger';
3+
import { IsEnum } from 'class-validator';
4+
5+
export class CreateMessageDto {
6+
@ApiProperty({
7+
description: `The role of the entity that is creating the message. Allowed values include:
8+
- user: Indicates the message is sent by an actual user and should be used in most cases to represent user-generated messages.
9+
- assistant: Indicates the message is generated by the assistant. Use this value to insert messages from the assistant into the conversation.`,
10+
})
11+
@IsEnum(ChatCompletionRole)
12+
role: ChatCompletionRole;
13+
14+
@ApiProperty({
15+
description: 'The text contents of the message.',
16+
})
17+
content: string;
18+
}

cortex-js/src/infrastructure/dtos/threads/create-thread.dto.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator';
1+
import { IsArray, ValidateNested } from 'class-validator';
22
import { Thread } from '@/domain/models/thread.interface';
33
import { CreateThreadAssistantDto } from './create-thread-assistant.dto';
44
import { Type } from 'class-transformer';
55
import { ApiProperty } from '@nestjs/swagger';
66

77
export class CreateThreadDto implements Partial<Thread> {
8-
@ApiProperty({ description: 'The title of the thread.' })
9-
@IsOptional()
10-
@IsString()
11-
title: string;
12-
138
@ApiProperty({ description: "The details of the thread's settings." })
149
@IsArray()
1510
@ValidateNested({ each: true })
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { PartialType } from '@nestjs/mapped-types';
2+
import { MessageEntity } from '@/infrastructure/entities/message.entity';
3+
4+
export class UpdateMessageDto extends PartialType(MessageEntity) {}

cortex-js/src/infrastructure/entities/message.entity.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,7 @@ export class MessageEntity implements Message {
4545

4646
@Column({ nullable: true })
4747
error_code?: ErrorCode;
48+
49+
@Column({ type: 'simple-json', nullable: true })
50+
attachments?: any[];
4851
}

cortex-js/src/usecases/assistants/assistants.usecases.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AssistantEntity } from '@/infrastructure/entities/assistant.entity';
33
import { Repository } from 'typeorm';
44
import { CreateAssistantDto } from '@/infrastructure/dtos/assistants/create-assistant.dto';
55
import { Assistant } from '@/domain/models/assistant.interface';
6+
import { PageDto } from '@/infrastructure/dtos/page.dto';
67

78
@Injectable()
89
export class AssistantsUsecases {
@@ -20,6 +21,43 @@ export class AssistantsUsecases {
2021
this.assistantRepository.insert(assistant);
2122
}
2223

24+
async listAssistants(
25+
limit: number,
26+
order: 'asc' | 'desc',
27+
after?: string,
28+
before?: string,
29+
) {
30+
const queryBuilder = this.assistantRepository.createQueryBuilder();
31+
const normalizedOrder = order === 'asc' ? 'ASC' : 'DESC';
32+
33+
queryBuilder.orderBy('created_at', normalizedOrder).take(limit + 1);
34+
35+
if (after) {
36+
queryBuilder.andWhere('id > :after', { after });
37+
}
38+
39+
if (before) {
40+
queryBuilder.andWhere('id < :before', { before });
41+
}
42+
43+
const { entities: assistants } = await queryBuilder.getRawAndEntities();
44+
45+
if (assistants.length === 0) {
46+
assistants.push(this.janAssistant);
47+
}
48+
49+
let hasMore = false;
50+
if (assistants.length > limit) {
51+
hasMore = true;
52+
assistants.pop();
53+
}
54+
55+
const firstId = assistants[0]?.id ?? undefined;
56+
const lastId = assistants[assistants.length - 1]?.id ?? undefined;
57+
58+
return new PageDto(assistants, hasMore, firstId, lastId);
59+
}
60+
2361
async findAll(): Promise<Assistant[]> {
2462
return this.assistantRepository.find();
2563
}

cortex-js/src/usecases/models/models.usecases.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ export class ModelsUsecases {
209209
});
210210
}
211211

212-
// TODO: NamH move to a helper or utils
213212
private isValidUrl(input: string | undefined): boolean {
214213
if (!input) return false;
215214
try {

0 commit comments

Comments
 (0)