@arcote.tech/arc-chat 0.5.0 → 0.5.2

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.
@@ -1,99 +1,259 @@
1
- import { context, type ArcContextElement, type ArcId } from "@arcote.tech/arc";
1
+ import {
2
+ context,
3
+ arcFunction,
4
+ type ArcContextElement,
5
+ type ArcId,
6
+ type ArcFunction,
7
+ type DefaultFunctionData,
8
+ type Merge,
9
+ type $type,
10
+ } from "@arcote.tech/arc";
2
11
  import type { AccountId, Token } from "@arcote.tech/arc-auth";
3
- import type { AIConfig } from "@arcote.tech/arc-ai";
4
- import type { ArcToolAny, ToolContext } from "@arcote.tech/arc-ai";
12
+ import type { AIConfig, ArcToolAny } from "@arcote.tech/arc-ai";
13
+ import { tool as createToolFactory } from "@arcote.tech/arc-ai";
14
+ import type { ArcTokenAny } from "@arcote.tech/arc";
15
+ import type { FnProtection, FnProtectionCheck } from "@arcote.tech/arc";
5
16
  import { createMessageId, createMessageAggregate } from "./aggregates/message";
6
- import { createAiGenerationListener } from "./listeners/ai-generation-listener";
17
+ import { createAiGenerationListener, createAiResumeListener } from "./listeners/ai-generation-listener";
7
18
  import { createChatStreamRoute } from "./routes/chat-stream-route";
8
- import { createToolResultsRoute } from "./routes/tool-results-route";
19
+ import { createChatComponent } from "./react/chat-component";
20
+ import type { ComponentType } from "react";
9
21
 
10
- // ─── Prepare Callback ───────────────────────────────────────────
22
+ // ─── Chat Data ──────────────────────────────────────────────────
11
23
 
12
- export interface PrepareContext {
13
- query: (element: ArcContextElement<any>) => any;
14
- mutate: (element: ArcContextElement<any>) => any;
24
+ export interface ArcChatData {
25
+ name: string;
26
+ identifyBy: ArcId<any> | null;
27
+ accountId: AccountId | null;
28
+ userToken: Token | null;
29
+ protectBy: Token | null;
30
+ protectByCheck: ((p: any) => any) | null;
31
+ ai: AIConfig | null;
32
+ instruction: ArcFunction<any> | null;
33
+ tools: ArcToolAny[];
34
+ maxExecutionCount: number;
35
+ toolChoice: "auto" | "required" | { type: "function"; name: string };
15
36
  }
16
37
 
17
- export interface PrepareParams {
18
- content: string;
19
- identifyBy: string;
20
- model: string;
21
- }
38
+ const defaultChatData = {
39
+ name: "",
40
+ identifyBy: null,
41
+ accountId: null,
42
+ userToken: null,
43
+ protectBy: null,
44
+ protectByCheck: null,
45
+ ai: null,
46
+ instruction: null,
47
+ tools: [],
48
+ maxExecutionCount: 10,
49
+ toolChoice: "auto" as const,
50
+ } as const satisfies ArcChatData;
22
51
 
23
- export interface PrepareResult {
24
- instructions: string;
25
- tools?: ArcToolAny[];
26
- clientTools?: ArcToolAny[];
27
- }
52
+ type DefaultChatData = typeof defaultChatData;
28
53
 
29
- // ─── Chat Factory ───────────────────────────────────────────────
54
+ // ─── ArcChat Builder ────────────────────────────────────────────
30
55
 
31
- export function chat(config: {
32
- name: string;
33
- identifyBy: ArcId<any>;
34
- accountId: AccountId;
35
- userToken: Token;
36
- ai: AIConfig;
37
- tools?: ArcToolAny[];
38
- clientTools?: ArcToolAny[];
39
- prepare?: ((ctx: PrepareContext, params: PrepareParams) => Promise<PrepareResult>) | false;
40
- maxExecutionCount?: number;
41
- }) {
42
- const messageId = createMessageId({ name: config.name });
43
-
44
- const Message = createMessageAggregate({
45
- name: config.name,
46
- messageId,
47
- scopeId: config.identifyBy,
48
- accountId: config.accountId,
49
- userToken: config.userToken,
50
- });
51
-
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);
56
+ export class ArcChat<const Data extends ArcChatData = DefaultChatData> {
57
+ readonly data: Data;
58
+
59
+ constructor(data: Data) {
60
+ this.data = data;
61
+ }
62
+
63
+ identifyBy<Id extends ArcId<any>>(id: Id) {
64
+ return new ArcChat<Merge<Data, { identifyBy: Id }>>({
65
+ ...this.data,
66
+ identifyBy: id,
67
+ } as any);
68
+ }
69
+
70
+ accountId(id: AccountId) {
71
+ return new ArcChat<Merge<Data, { accountId: typeof id }>>({
72
+ ...this.data,
73
+ accountId: id,
74
+ } as any);
75
+ }
76
+
77
+ userToken(token: Token) {
78
+ return new ArcChat<Merge<Data, { userToken: typeof token }>>({
79
+ ...this.data,
80
+ userToken: token,
81
+ } as any);
82
+ }
83
+
84
+ protectBy<T extends ArcTokenAny>(token: T, check: FnProtectionCheck<T>) {
85
+ return new ArcChat<Merge<Data, { protectBy: T; protectByCheck: typeof check }>>({
86
+ ...this.data,
87
+ protectBy: token,
88
+ protectByCheck: check,
89
+ } as any);
90
+ }
91
+
92
+ ai(config: AIConfig) {
93
+ return new ArcChat<Merge<Data, { ai: AIConfig }>>({
94
+ ...this.data,
95
+ ai: config,
96
+ } as any);
97
+ }
98
+
99
+ instruction(
100
+ configure: (fn: ArcFunction<DefaultFunctionData>) => ArcFunction<any>,
101
+ ) {
102
+ const instructionFn = configure(arcFunction());
103
+ return new ArcChat<Merge<Data, { instruction: typeof instructionFn }>>({
104
+ ...this.data,
105
+ instruction: instructionFn,
106
+ } as any);
107
+ }
108
+
109
+ maxExecutionCount(count: number) {
110
+ return new ArcChat<Merge<Data, { maxExecutionCount: typeof count }>>({
111
+ ...this.data,
112
+ maxExecutionCount: count,
113
+ } as any);
114
+ }
115
+
116
+ toolChoice(choice: "auto" | "required" | { type: "function"; name: string }) {
117
+ return new ArcChat<Merge<Data, { toolChoice: typeof choice }>>({
118
+ ...this.data,
119
+ toolChoice: choice,
120
+ } as any);
121
+ }
122
+
123
+ createTool<const N extends string>(name: N) {
124
+ type IdType = Data["identifyBy"] extends ArcId<any>
125
+ ? $type<Data["identifyBy"]>
126
+ : string;
127
+ const t = createToolFactory(name);
128
+ if (this.data.identifyBy) {
129
+ return t.identifyBy(this.data.identifyBy) as any as ReturnType<typeof t.identifyBy> & { __brand: IdType };
130
+ }
131
+ return t as any;
132
+ }
133
+
134
+ useTools<Tools extends ArcToolAny[]>(tools: Tools) {
135
+ return new ArcChat<Merge<Data, { tools: Tools }>>({
136
+ ...this.data,
137
+ tools,
138
+ } as any);
139
+ }
140
+
141
+ build() {
142
+ const {
143
+ name,
144
+ identifyBy,
145
+ accountId,
146
+ userToken,
147
+ protectBy: protectByToken,
148
+ protectByCheck,
149
+ ai: aiConfig,
150
+ instruction: instructionFn,
151
+ tools,
152
+ maxExecutionCount,
153
+ toolChoice,
154
+ } = this.data;
155
+
156
+ if (!name) throw new Error("ArcChat: name is required");
157
+ if (!identifyBy) throw new Error("ArcChat: identifyBy is required");
158
+ if (!accountId) throw new Error("ArcChat: accountId is required");
159
+ if (!userToken) throw new Error("ArcChat: userToken is required");
160
+ if (!aiConfig) throw new Error("ArcChat: ai is required");
161
+
162
+ const messageId = createMessageId({ name });
163
+
164
+ const Message = createMessageAggregate({
165
+ name,
166
+ messageId,
167
+ scopeId: identifyBy,
168
+ accountId,
169
+ userToken: protectByToken ?? userToken,
170
+ });
171
+
172
+ // Collect query/mutate from instruction + tools
173
+ const allQueryElements: ArcContextElement<any>[] = [];
174
+ const allMutationElements: ArcContextElement<any>[] = [];
175
+
176
+ if (instructionFn) {
177
+ for (const el of instructionFn.data.queryElements || []) {
178
+ if (!allQueryElements.includes(el)) allQueryElements.push(el);
179
+ }
180
+ }
181
+
182
+ for (const t of tools) {
183
+ for (const el of t.queryElements) {
184
+ if (!allQueryElements.includes(el)) allQueryElements.push(el);
185
+ }
186
+ for (const el of t.mutationElements) {
187
+ if (!allMutationElements.includes(el)) allMutationElements.push(el);
59
188
  }
60
189
  }
190
+
191
+ const serverTools = tools.filter((t) => t.isServerTool);
192
+ const interactiveTools = tools.filter((t) => t.isInteractiveTool);
193
+
194
+ // Add ledger element to mutation deps if billing configured
195
+ const billingElements: ArcContextElement<any>[] = [];
196
+ if (aiConfig.billing) {
197
+ for (const el of aiConfig.billing.ledger.elements) {
198
+ if (!allMutationElements.includes(el)) allMutationElements.push(el);
199
+ if (!allQueryElements.includes(el)) allQueryElements.push(el);
200
+ billingElements.push(el);
201
+ }
202
+ }
203
+
204
+ const listenerConfig = {
205
+ name,
206
+ messageElement: Message,
207
+ resolveProvider: aiConfig.resolveProvider,
208
+ instruction: instructionFn ?? undefined,
209
+ serverTools,
210
+ interactiveTools,
211
+ allQueryElements,
212
+ allMutationElements,
213
+ maxExecutionCount,
214
+ toolChoice: toolChoice !== "auto" ? toolChoice : undefined,
215
+ };
216
+
217
+ const aiListener = createAiGenerationListener(listenerConfig);
218
+ const aiResumeListener = createAiResumeListener(listenerConfig);
219
+
220
+ const streamRoute = createChatStreamRoute({
221
+ name,
222
+ userToken,
223
+ });
224
+
225
+ const elements: ArcContextElement<any>[] = [
226
+ Message,
227
+ aiListener,
228
+ aiResumeListener,
229
+ streamRoute,
230
+ ];
231
+
232
+ function toReactComponent(): ComponentType<{ scope: any; identifyBy: string }> {
233
+ return createChatComponent({
234
+ chatName: name,
235
+ tools,
236
+ messageElementName: `${name}Messages`,
237
+ });
238
+ }
239
+
240
+ return {
241
+ context: context(elements),
242
+ elements,
243
+ messageId,
244
+ Message,
245
+ toReactComponent,
246
+ };
61
247
  }
248
+ }
249
+
250
+ // ─── Factory ────────────────────────────────────────────────────
62
251
 
63
- const aiListener = createAiGenerationListener({
64
- name: config.name,
65
- messageElement: Message,
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,
72
- });
73
-
74
- const streamRoute = createChatStreamRoute({
75
- name: config.name,
76
- userToken: config.userToken,
77
- });
78
-
79
- const toolResultsRoute = createToolResultsRoute({
80
- name: config.name,
81
- userToken: config.userToken,
82
- });
83
-
84
- const elements: ArcContextElement<any>[] = [
85
- Message,
86
- aiListener,
87
- streamRoute,
88
- toolResultsRoute,
89
- ];
90
-
91
- return {
92
- context: context(elements),
93
- elements,
94
- messageId,
95
- Message,
96
- };
252
+ export function chat<const Name extends string>(name: Name) {
253
+ return new ArcChat<Merge<DefaultChatData, { name: Name }>>({
254
+ ...defaultChatData,
255
+ name,
256
+ } as any);
97
257
  }
98
258
 
99
- export type ChatConfig = ReturnType<typeof chat>;
259
+ export type ChatConfig = ReturnType<ArcChat<any>["build"]>;
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // --- Builder API ---
2
- export { chat } from "./chat-builder";
3
- export type { ChatConfig, PrepareContext, PrepareParams, PrepareResult } from "./chat-builder";
2
+ export { chat, ArcChat } from "./chat-builder";
3
+ export type { ChatConfig, ArcChatData } from "./chat-builder";
4
4
 
5
5
  // --- Aggregate factories & types ---
6
6
  export { createMessageAggregate, createMessageId } from "./aggregates/message";
@@ -20,24 +20,6 @@ export type { AiGenerationListenerConfig } from "./listeners/ai-generation-liste
20
20
 
21
21
  // --- Routes ---
22
22
  export { createChatStreamRoute } from "./routes/chat-stream-route";
23
- export { createToolResultsRoute } from "./routes/tool-results-route";
24
23
 
25
- // --- React components & hooks ---
26
- export {
27
- Chat,
28
- ChatMessage,
29
- ChatInput,
30
- QuestionTabs,
31
- ToolUseBlock,
32
- useChat,
33
- } from "./react";
34
- export type {
35
- ChatMessageData,
36
- ChatModel,
37
- SendMessageOptions,
38
- ToolUse,
39
- Question,
40
- QuestionAnswers,
41
- UseChatConfig,
42
- UseChatReturn,
43
- } from "./react";
24
+ // --- Reusable tools ---
25
+ export { askQuestions } from "./tools/ask-questions";