@arcote.tech/arc-chat 0.4.9 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-chat",
3
3
  "type": "module",
4
- "version": "0.4.9",
4
+ "version": "0.5.0",
5
5
  "private": false,
6
6
  "description": "Chat module with AI integration for Arc framework",
7
7
  "main": "./src/index.ts",
@@ -10,11 +10,11 @@
10
10
  "type-check": "tsc --noEmit"
11
11
  },
12
12
  "peerDependencies": {
13
- "@arcote.tech/arc": "^0.4.9",
14
- "@arcote.tech/arc-ai": "^0.4.9",
15
- "@arcote.tech/arc-auth": "^0.4.9",
16
- "@arcote.tech/arc-ds": "^0.4.9",
17
- "@arcote.tech/platform": "^0.4.9",
13
+ "@arcote.tech/arc": "^0.5.0",
14
+ "@arcote.tech/arc-ai": "^0.5.0",
15
+ "@arcote.tech/arc-auth": "^0.5.0",
16
+ "@arcote.tech/arc-ds": "^0.5.0",
17
+ "@arcote.tech/platform": "^0.5.0",
18
18
  "lucide-react": ">=0.400.0",
19
19
  "react": ">=18.0.0",
20
20
  "typescript": "^5.0.0"
@@ -7,7 +7,7 @@ import {
7
7
  type ArcId,
8
8
  } from "@arcote.tech/arc";
9
9
  import type { Token } from "@arcote.tech/arc-auth";
10
- import type { LLMProvider } from "@arcote.tech/arc-ai";
10
+ import { createStreamSession } from "../streaming/stream-registry";
11
11
 
12
12
  // ─── ID ──────────────────────────────────────────────────────────
13
13
 
@@ -24,10 +24,9 @@ export type MessageId<Name extends string = string> = ReturnType<
24
24
  export type MessageAggregateData = {
25
25
  name: string;
26
26
  messageId: ArcId<any>;
27
- conversationId: ArcId<any>;
27
+ scopeId: ArcId<any>;
28
28
  accountId: ArcId<any>;
29
29
  userToken: Token;
30
- resolveProvider: (model: string) => LLMProvider | undefined;
31
30
  };
32
31
 
33
32
  export const createMessageAggregate = <
@@ -35,31 +34,35 @@ export const createMessageAggregate = <
35
34
  >(
36
35
  data: Data,
37
36
  ) => {
38
- const { messageId, conversationId, userToken, resolveProvider } = data;
37
+ const { messageId, scopeId, userToken } = data;
39
38
 
40
39
  return aggregate(`${data.name}Messages`, messageId, {
41
- conversationId,
40
+ scopeId,
42
41
  role: string(),
43
42
  content: string(),
43
+ model: string().optional(),
44
44
  toolCalls: string().optional(),
45
45
  toolResults: string().optional(),
46
- completionId: string().optional(),
46
+ usage: string().optional(),
47
47
  createdAt: date(),
48
48
  })
49
49
  .publicEvent(
50
50
  "messageSent",
51
51
  {
52
52
  messageId,
53
- conversationId,
53
+ scopeId,
54
+ sessionId: string(),
54
55
  role: string(),
55
56
  content: string(),
57
+ model: string(),
56
58
  },
57
59
  async (ctx, event) => {
58
60
  const p = event.payload;
59
61
  await ctx.set(p.messageId, {
60
- conversationId: p.conversationId,
62
+ scopeId: p.scopeId,
61
63
  role: p.role,
62
64
  content: p.content,
65
+ model: p.model,
63
66
  createdAt: event.createdAt,
64
67
  });
65
68
  },
@@ -69,21 +72,24 @@ export const createMessageAggregate = <
69
72
  "assistantMessageCompleted",
70
73
  {
71
74
  messageId,
72
- conversationId,
75
+ scopeId,
76
+ sessionId: string(),
73
77
  content: string(),
74
- completionId: string().optional(),
78
+ model: string().optional(),
75
79
  toolCalls: string().optional(),
76
80
  toolResults: string().optional(),
81
+ usage: string().optional(),
77
82
  },
78
83
  async (ctx, event) => {
79
84
  const p = event.payload;
80
85
  await ctx.set(p.messageId, {
81
- conversationId: p.conversationId,
86
+ scopeId: p.scopeId,
82
87
  role: "assistant",
83
88
  content: p.content,
84
- completionId: p.completionId,
89
+ model: p.model,
85
90
  toolCalls: p.toolCalls,
86
91
  toolResults: p.toolResults,
92
+ usage: p.usage,
87
93
  createdAt: event.createdAt,
88
94
  });
89
95
  },
@@ -91,88 +97,72 @@ export const createMessageAggregate = <
91
97
 
92
98
  .mutateMethod(
93
99
  "sendMessage",
94
- {
95
- params: {
96
- conversationId,
100
+ (fn) => fn.withParams({
101
+ scopeId,
97
102
  content: string().minLength(1),
98
- model: string().optional(),
99
- tools: string().optional(),
100
- webSearch: string().optional(),
101
- },
102
- },
103
+ model: string(),
104
+ }).handle(
103
105
  ONLY_SERVER &&
104
106
  (async (ctx, params) => {
105
107
  const userMsgId = messageId.generate();
108
+ const sessionId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
109
+
110
+ createStreamSession(sessionId);
111
+
106
112
  await ctx.messageSent.emit({
107
113
  messageId: userMsgId,
108
- conversationId: params.conversationId,
114
+ scopeId: params.scopeId,
115
+ sessionId,
109
116
  role: "user",
110
117
  content: params.content,
118
+ model: params.model,
111
119
  });
112
120
 
113
- const history = await ctx.$query.find({
114
- where: { conversationId: params.conversationId },
115
- });
121
+ return {
122
+ messageId: userMsgId,
123
+ sessionId,
124
+ };
125
+ }),
126
+ ))
116
127
 
117
- const messages = history.map((msg) => ({
118
- role: msg.role as "user" | "assistant" | "system" | "tool",
119
- content: msg.content,
120
- }));
121
-
122
- messages.push({ role: "user", content: params.content });
123
-
124
- const model = params.model ?? "gpt-4o";
125
-
126
- const provider = resolveProvider(model);
127
- if (!provider) {
128
- return { error: "PROVIDER_NOT_FOUND" as const };
129
- }
130
-
131
- try {
132
- const toolDefs = params.tools
133
- ? JSON.parse(params.tools)
134
- : undefined;
135
- const webSearch = params.webSearch === "true";
136
-
137
- const result = await provider.complete({
138
- model,
139
- messages,
140
- tools: toolDefs,
141
- webSearch,
142
- });
143
-
144
- const assistantMsgId = messageId.generate();
145
- await ctx.assistantMessageCompleted.emit({
146
- messageId: assistantMsgId,
147
- conversationId: params.conversationId,
148
- content: result.content,
149
- toolCalls: result.toolCalls.length
150
- ? JSON.stringify(result.toolCalls)
151
- : undefined,
152
- });
153
-
154
- return {
155
- messageId: assistantMsgId,
156
- content: result.content,
157
- toolCalls: result.toolCalls,
158
- finishReason: result.finishReason,
159
- };
160
- } catch (error) {
161
- const errorMessage =
162
- error instanceof Error ? error.message : "Unknown error";
163
- return { error: errorMessage };
164
- }
128
+ .mutateMethod(
129
+ "completeAssistantMessage",
130
+ (fn) => fn.withParams({
131
+ scopeId,
132
+ sessionId: string(),
133
+ content: string(),
134
+ model: string().optional(),
135
+ toolCalls: string().optional(),
136
+ toolResults: string().optional(),
137
+ usage: string().optional(),
138
+ }).handle(
139
+ ONLY_SERVER &&
140
+ (async (ctx, params) => {
141
+ const assistantMsgId = messageId.generate();
142
+ await ctx.assistantMessageCompleted.emit({
143
+ messageId: assistantMsgId,
144
+ scopeId: params.scopeId,
145
+ sessionId: params.sessionId,
146
+ content: params.content,
147
+ model: params.model,
148
+ toolCalls: params.toolCalls,
149
+ toolResults: params.toolResults,
150
+ usage: params.usage,
151
+ });
152
+ return { messageId: assistantMsgId };
165
153
  }),
166
- )
154
+ ))
167
155
 
168
156
  .clientQuery(
169
- "getByConversation",
170
- async (ctx, params: { conversationId: string }) =>
171
- ctx.$query.find({ where: { conversationId: params.conversationId } }),
157
+ "getByScope",
158
+ (fn) => fn
159
+ .withParams({ scopeId: string() })
160
+ .handle(async (ctx, params) =>
161
+ ctx.$query.find({ where: { scopeId: params.scopeId } }),
162
+ ),
172
163
  )
173
164
 
174
- .protectBy(userToken, (p) => ({ accountId: p.accountId }))
175
- ;
165
+ .protectBy(userToken, (p) => ({ accountId: p.accountId }));
176
166
  };
177
167
 
178
168
  export type MessageAggregate = ReturnType<typeof createMessageAggregate>;
@@ -1,75 +1,99 @@
1
- import {
2
- context,
3
- type ArcAggregateElement,
4
- type ArcContextElement,
5
- } from "@arcote.tech/arc";
1
+ import { context, type ArcContextElement, type ArcId } from "@arcote.tech/arc";
6
2
  import type { AccountId, Token } from "@arcote.tech/arc-auth";
7
- import type { AIBuilder } from "@arcote.tech/arc-ai";
8
- import { createConversationId, createConversationAggregate } from "./aggregates/conversation";
3
+ import type { AIConfig } from "@arcote.tech/arc-ai";
4
+ import type { ArcToolAny, ToolContext } from "@arcote.tech/arc-ai";
9
5
  import { createMessageId, createMessageAggregate } from "./aggregates/message";
6
+ import { createAiGenerationListener } from "./listeners/ai-generation-listener";
7
+ import { createChatStreamRoute } from "./routes/chat-stream-route";
8
+ import { createToolResultsRoute } from "./routes/tool-results-route";
10
9
 
11
- export class ChatBuilder<
12
- ConvId,
13
- MsgId,
14
- Conversation extends ArcAggregateElement,
15
- Message extends ArcAggregateElement,
16
- Elements extends ArcContextElement<any>[],
17
- > {
18
- constructor(
19
- private readonly _name: string,
20
- readonly conversationId: ConvId,
21
- readonly messageId: MsgId,
22
- readonly Conversation: Conversation,
23
- readonly Message: Message,
24
- readonly elements: Elements,
25
- ) {}
10
+ // ─── Prepare Callback ───────────────────────────────────────────
26
11
 
27
- build() {
28
- return {
29
- context: context(this.elements),
30
- conversationId: this.conversationId,
31
- messageId: this.messageId,
32
- Conversation: this.Conversation,
33
- Message: this.Message,
34
- };
35
- }
12
+ export interface PrepareContext {
13
+ query: (element: ArcContextElement<any>) => any;
14
+ mutate: (element: ArcContextElement<any>) => any;
15
+ }
16
+
17
+ export interface PrepareParams {
18
+ content: string;
19
+ identifyBy: string;
20
+ model: string;
36
21
  }
37
22
 
38
- export function chat<const Name extends string>(config: {
39
- name: Name;
23
+ export interface PrepareResult {
24
+ instructions: string;
25
+ tools?: ArcToolAny[];
26
+ clientTools?: ArcToolAny[];
27
+ }
28
+
29
+ // ─── Chat Factory ───────────────────────────────────────────────
30
+
31
+ export function chat(config: {
32
+ name: string;
33
+ identifyBy: ArcId<any>;
40
34
  accountId: AccountId;
41
35
  userToken: Token;
42
- ai: ReturnType<AIBuilder<any, any, any, any, any, any>["build"]>;
36
+ ai: AIConfig;
37
+ tools?: ArcToolAny[];
38
+ clientTools?: ArcToolAny[];
39
+ prepare?: ((ctx: PrepareContext, params: PrepareParams) => Promise<PrepareResult>) | false;
40
+ maxExecutionCount?: number;
43
41
  }) {
44
- const conversationId = createConversationId({ name: config.name });
45
42
  const messageId = createMessageId({ name: config.name });
46
43
 
47
- const Conversation = createConversationAggregate({
44
+ const Message = createMessageAggregate({
48
45
  name: config.name,
49
- conversationId,
46
+ messageId,
47
+ scopeId: config.identifyBy,
50
48
  accountId: config.accountId,
51
49
  userToken: config.userToken,
52
50
  });
53
- const conversationElement = Conversation;
54
51
 
55
- const Message = createMessageAggregate({
52
+ // Collect all mutation elements from server tools for listener registration
53
+ const allTools = config.tools ?? [];
54
+ const allMutationElements: ArcContextElement<any>[] = [];
55
+ for (const tool of allTools) {
56
+ for (const el of tool.mutationElements) {
57
+ if (!allMutationElements.includes(el)) {
58
+ allMutationElements.push(el);
59
+ }
60
+ }
61
+ }
62
+
63
+ const aiListener = createAiGenerationListener({
56
64
  name: config.name,
57
- messageId,
58
- conversationId,
59
- accountId: config.accountId,
60
- userToken: config.userToken,
65
+ messageElement: Message,
61
66
  resolveProvider: config.ai.resolveProvider,
67
+ prepare: config.prepare || undefined,
68
+ tools: allTools,
69
+ clientTools: config.clientTools ?? [],
70
+ toolMutationElements: allMutationElements,
71
+ maxExecutionCount: config.maxExecutionCount ?? 10,
62
72
  });
63
- const messageElement = Message;
64
73
 
65
- const elements = [conversationElement, messageElement];
74
+ const streamRoute = createChatStreamRoute({
75
+ name: config.name,
76
+ userToken: config.userToken,
77
+ });
66
78
 
67
- return new ChatBuilder(
68
- config.name,
69
- conversationId,
70
- messageId,
71
- Conversation,
79
+ const toolResultsRoute = createToolResultsRoute({
80
+ name: config.name,
81
+ userToken: config.userToken,
82
+ });
83
+
84
+ const elements: ArcContextElement<any>[] = [
72
85
  Message,
86
+ aiListener,
87
+ streamRoute,
88
+ toolResultsRoute,
89
+ ];
90
+
91
+ return {
92
+ context: context(elements),
73
93
  elements,
74
- );
94
+ messageId,
95
+ Message,
96
+ };
75
97
  }
98
+
99
+ export type ChatConfig = ReturnType<typeof chat>;
package/src/index.ts CHANGED
@@ -1,19 +1,35 @@
1
1
  // --- Builder API ---
2
- export { chat, ChatBuilder } from "./chat-builder";
2
+ export { chat } from "./chat-builder";
3
+ export type { ChatConfig, PrepareContext, PrepareParams, PrepareResult } from "./chat-builder";
3
4
 
4
5
  // --- Aggregate factories & types ---
5
- export { createConversationAggregate, createConversationId } from "./aggregates/conversation";
6
- export type { ConversationAggregate, ConversationId } from "./aggregates/conversation";
7
6
  export { createMessageAggregate, createMessageId } from "./aggregates/message";
8
7
  export type { MessageAggregate, MessageId } from "./aggregates/message";
9
8
 
10
- // --- React components ---
9
+ // --- Streaming ---
10
+ export {
11
+ createStreamSession,
12
+ getStreamSession,
13
+ deleteStreamSession,
14
+ } from "./streaming/stream-registry";
15
+ export type { StreamSession } from "./streaming/stream-registry";
16
+
17
+ // --- Listener ---
18
+ export { createAiGenerationListener } from "./listeners/ai-generation-listener";
19
+ export type { AiGenerationListenerConfig } from "./listeners/ai-generation-listener";
20
+
21
+ // --- Routes ---
22
+ export { createChatStreamRoute } from "./routes/chat-stream-route";
23
+ export { createToolResultsRoute } from "./routes/tool-results-route";
24
+
25
+ // --- React components & hooks ---
11
26
  export {
12
27
  Chat,
13
28
  ChatMessage,
14
29
  ChatInput,
15
30
  QuestionTabs,
16
31
  ToolUseBlock,
32
+ useChat,
17
33
  } from "./react";
18
34
  export type {
19
35
  ChatMessageData,
@@ -22,4 +38,6 @@ export type {
22
38
  ToolUse,
23
39
  Question,
24
40
  QuestionAnswers,
41
+ UseChatConfig,
42
+ UseChatReturn,
25
43
  } from "./react";