startx 0.9.4 → 0.9.8

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.
Files changed (39) hide show
  1. package/apps/startx-cli/dist/index.mjs +52 -52
  2. package/configs/eslint-config/src/configs/base.ts +26 -11
  3. package/package.json +2 -2
  4. package/packages/@db/drizzle/tsconfig.json +2 -8
  5. package/packages/@db/sqlite/tsconfig.json +2 -8
  6. package/packages/@repo/env/package.json +2 -1
  7. package/packages/@repo/env/src/utils.ts +7 -6
  8. package/packages/@repo/lib/src/events/i-event.ts +1 -1
  9. package/packages/aix/eslint.config.ts +4 -0
  10. package/packages/aix/package.json +53 -0
  11. package/packages/aix/src/aix.ts +519 -0
  12. package/packages/aix/src/index.ts +3 -0
  13. package/packages/aix/src/lib/convertor/schema-convertor.ts +108 -0
  14. package/packages/aix/src/lib/convertor/variable-resolver.ts +161 -0
  15. package/packages/aix/src/lib/tokenizer/index.ts +1 -0
  16. package/packages/aix/src/lib/tokenizer/tokenizer.ts +42 -0
  17. package/packages/aix/src/providers/ai-chat.ts +25 -0
  18. package/packages/aix/src/providers/ai-event.ts +21 -0
  19. package/packages/aix/src/providers/ai-interface.ts +236 -0
  20. package/packages/aix/src/providers/ai-prompt.ts +14 -0
  21. package/packages/aix/src/providers/default-models.ts +471 -0
  22. package/packages/aix/src/providers/index.ts +1 -0
  23. package/packages/aix/src/providers/openai/openai.ts +139 -0
  24. package/packages/aix/src/providers/providers.ts +39 -0
  25. package/packages/aix/src/providers/types.ts +54 -0
  26. package/packages/aix/src/tools/generic/database.ts +290 -0
  27. package/packages/aix/src/tools/generic/forecast.ts +216 -0
  28. package/packages/aix/src/tools/generic/index.ts +4 -0
  29. package/packages/aix/src/tools/generic/planner.ts +114 -0
  30. package/packages/aix/src/tools/generic/summarizer.ts +101 -0
  31. package/packages/aix/src/tools/i-tool.ts +33 -0
  32. package/packages/aix/src/tools/index.ts +2 -0
  33. package/packages/aix/src/tools/system/index.ts +297 -0
  34. package/packages/aix/src/tools/tool-manager.ts +241 -0
  35. package/packages/aix/src/tools/types.ts +109 -0
  36. package/packages/aix/tsconfig.json +7 -0
  37. package/packages/aix/vitest.config.ts +3 -0
  38. package/pnpm-workspace.yaml +8 -0
  39. package/turbo.json +0 -1
@@ -0,0 +1,519 @@
1
+ import { IEvent, type IEventWithPayload } from "@repo/lib/events";
2
+ import { logger } from "@repo/logger";
3
+ import type z from "zod";
4
+ import type { AiInterfaceConstructor } from "./providers/ai-interface.js";
5
+ import type { DefaultAiModels } from "./providers/default-models.js";
6
+ import { aiProvider } from "./providers/providers.js";
7
+ import type { AiProvider } from "./providers/types.js";
8
+ import { ITool } from "./tools/i-tool.js";
9
+ import { PlannerTool, SummarizerTool } from "./tools/index.js";
10
+ import { ToolManager } from "./tools/tool-manager.js";
11
+ import type { TInternal, ToolConnector } from "./tools/types.js";
12
+
13
+ export class Aix {
14
+ static client = <T extends AiProvider>(type: T) => aiProvider<T>(type);
15
+
16
+ static summarizer = async <T extends AiProvider>(props: {
17
+ provider: T;
18
+ model: (typeof DefaultAiModels)[T][number]["id"];
19
+ credentials: AiInterfaceConstructor["credentials"];
20
+ message: string;
21
+ }) => {
22
+ const ai = this.client(props.provider)({
23
+ model: props.model,
24
+ credentials: props.credentials,
25
+ conversations: [],
26
+ preferences: {
27
+ name: "summarizer",
28
+ type: "worker",
29
+ toolOnly: true,
30
+ },
31
+ whitelistedTools: ["general-summarizer"],
32
+ });
33
+
34
+ await ai.tools.attachTools({
35
+ type: "internal",
36
+ tool: SummarizerTool,
37
+ isInternal: true,
38
+ });
39
+
40
+ const response = await ai.handleUserMessage(props.message);
41
+ const messages = response.messages;
42
+ const summary = JSON.parse(messages[messages.length - 1].content) as {
43
+ summary: string;
44
+ };
45
+
46
+ return {
47
+ summary: summary?.summary,
48
+ token: response.token,
49
+ };
50
+ };
51
+
52
+ static querySummarizer = async <T extends AiProvider>(props: {
53
+ provider: T;
54
+ model: (typeof DefaultAiModels)[T][number]["id"];
55
+ credentials: AiInterfaceConstructor["credentials"];
56
+ message: string;
57
+ }) => {
58
+ const ai = this.client(props.provider)({
59
+ model: props.model,
60
+ credentials: props.credentials,
61
+ conversations: [],
62
+ preferences: {
63
+ name: "summarizer",
64
+ type: "worker",
65
+ toolOnly: true,
66
+ },
67
+ whitelistedTools: ["query-summarizer"],
68
+ });
69
+
70
+ await ai.tools.attachTools({
71
+ type: "internal",
72
+ tool: SummarizerTool,
73
+ isInternal: true,
74
+ });
75
+
76
+ const response = await ai.handleUserMessage(props.message);
77
+ const messages = response.messages;
78
+ const summary = JSON.parse(messages[messages.length - 1].content) as
79
+ | {
80
+ isFeasible: true;
81
+ intentSummary: string;
82
+ executionPlan: string;
83
+ requiredTools: string[];
84
+ }
85
+ | {
86
+ isFeasible: false;
87
+ abortReason: string;
88
+ intentSummary: string;
89
+ };
90
+
91
+ return {
92
+ summary,
93
+ token: response.token,
94
+ };
95
+ };
96
+
97
+ static planner = async <T extends AiProvider>(props: {
98
+ provider: T;
99
+ model: (typeof DefaultAiModels)[T][number]["id"];
100
+ credentials: AiInterfaceConstructor["credentials"];
101
+ message: string;
102
+ maxTokens?: {
103
+ input: number;
104
+ output: number;
105
+ };
106
+ }) => {
107
+ const ai = this.client(props.provider)({
108
+ model: props.model,
109
+ credentials: props.credentials,
110
+ conversations: [],
111
+ preferences: {
112
+ name: "summarizer",
113
+ type: "worker",
114
+ temperature: 0.1,
115
+ toolOnly: true,
116
+ },
117
+ tokens: {
118
+ maxLimit: props.maxTokens,
119
+ },
120
+ whitelistedTools: ["planner"],
121
+ });
122
+
123
+ await ai.tools.attachTools({
124
+ type: "internal",
125
+ tool: PlannerTool,
126
+ isInternal: true,
127
+ });
128
+
129
+ const response = await ai.handleUserMessage(props.message);
130
+ const messages = response.messages;
131
+ const plan = JSON.parse(messages[messages.length - 1].content) as {
132
+ isFeasible: boolean;
133
+ queryClassification: "direct_answer" | "requires_tools" | "unclear";
134
+ intentSummary: string;
135
+ directResponse?: string;
136
+ abortReason?: string;
137
+ executionPhases?: Array<{
138
+ phaseId: string;
139
+ parallelNodes: Array<{
140
+ nodeId: string;
141
+ schemaOnly: boolean;
142
+ instruction: string;
143
+ tools: string[];
144
+ dependsOn?: string[] | undefined;
145
+ }>;
146
+ }>;
147
+ };
148
+
149
+ return {
150
+ plan,
151
+ token: response.token,
152
+ };
153
+ };
154
+
155
+ static executeWithPlanner = async <T extends AiProvider, Q extends AiProvider>(
156
+ props: ExecuteWithPlannerProps<T, Q>,
157
+ onEvent?: (props: IEventWithPayload<ExecuteWithPlannerEvents>) => void
158
+ ) => {
159
+ const event = new IEvent<ExecuteWithPlannerEvents>();
160
+
161
+ if (onEvent) {
162
+ event.onEvery(e => onEvent(e));
163
+ }
164
+
165
+ const tokenCount = { input: 0, output: 0 };
166
+
167
+ try {
168
+ event.emit("status", "planning");
169
+ const tools = new ToolManager({
170
+ whitelist: props.whitelist,
171
+ });
172
+
173
+ await Promise.all(
174
+ props.tools.map(async tool => {
175
+ await tools.attachTools(tool);
176
+ })
177
+ );
178
+
179
+ const pQuery = await this.planner({
180
+ model: props.planner.model,
181
+ provider: props.planner.provider,
182
+ credentials: props.planner.credentials,
183
+ maxTokens: props.planner.maxToken ?? {
184
+ input: 20000,
185
+ output: 20000,
186
+ },
187
+ message: `
188
+ System message: ${props.systemMessage}
189
+ User query: ${props.query}
190
+ Tools_available: ${tools
191
+ .getActiveTools()
192
+ .map(t => `${t.name}: ${t.description}`)
193
+ .join(", ")}
194
+ Always end the plan with the end_response or execute_javascript tool unless answering directly.
195
+ `,
196
+ });
197
+
198
+ tokenCount.input += pQuery.token?.input || 0;
199
+ tokenCount.output += pQuery.token?.output || 0;
200
+ event.emit("token", { ...tokenCount });
201
+
202
+ if (!pQuery.plan?.isFeasible || pQuery.plan?.queryClassification === "unclear") {
203
+ event.emit("status", "failed");
204
+ event.emit("error", {
205
+ reason: pQuery.plan?.abortReason || "Plan is not feasible",
206
+ });
207
+ return {
208
+ result: pQuery.plan?.abortReason || "Plan is not feasible",
209
+ token: tokenCount,
210
+ };
211
+ }
212
+
213
+ if (pQuery.plan.queryClassification === "direct_answer" && pQuery.plan.directResponse) {
214
+ event.emit("response", pQuery.plan.directResponse);
215
+ event.emit("status", "completed");
216
+ return {
217
+ result: pQuery.plan.directResponse,
218
+ token: tokenCount,
219
+ };
220
+ }
221
+
222
+ event.emit("plan", {
223
+ title: "Execution Plan Summary",
224
+ description: pQuery.plan.intentSummary,
225
+ executionPhases: pQuery.plan.executionPhases,
226
+ queryClassification: pQuery.plan.queryClassification,
227
+ });
228
+
229
+ const executionMatrix = (pQuery.plan.executionPhases || []).map(phase =>
230
+ phase.parallelNodes.map(node => node.nodeId)
231
+ );
232
+ event.emit("execution", executionMatrix);
233
+ event.emit("status", "executing");
234
+
235
+ const nodeResults = new Map<
236
+ string,
237
+ {
238
+ name: string;
239
+ response: string;
240
+ token: {
241
+ input: number;
242
+ output: number;
243
+ };
244
+ schemaOnly: boolean;
245
+ vars?: Array<{
246
+ name: string;
247
+ data: string;
248
+ schema: object;
249
+ }>;
250
+ }
251
+ >();
252
+
253
+ for (const phase of pQuery.plan.executionPhases || []) {
254
+ const phasePromises = phase.parallelNodes.map(async node => {
255
+ event.emit("current", node.nodeId);
256
+ const deps = (node.dependsOn?.map(dep => nodeResults.get(dep)!) || []).filter(e => e);
257
+
258
+ const runner = Aix.client(props.provider)({
259
+ conversations: [],
260
+ model: props.model,
261
+ credentials: props.credentials,
262
+ preferences: {
263
+ name: `Node Worker: ${node.nodeId}`,
264
+ type: "worker",
265
+ toolOnly: true,
266
+ temperature: 0.1,
267
+ },
268
+ tokens: {
269
+ maxLimit: { input: props.maxTokenPerExecution.input, output: props.maxTokenPerExecution.output },
270
+ current: { input: 0, output: 0 },
271
+ total: { input: 0, output: 0 },
272
+ },
273
+ internal: {
274
+ system: { ...props.internal?.system, schemaOnly: node.schemaOnly },
275
+ vars: new Map(deps.flatMap(dep => dep.vars?.map(e => [e.name, e]) || [])),
276
+ },
277
+ whitelistedTools: [...node.tools, "execute_javascript", "end_response"],
278
+ });
279
+
280
+ await Promise.all(
281
+ props.tools.map(async tool => {
282
+ await runner.tools.attachTools(tool);
283
+ })
284
+ );
285
+
286
+ let isolatedPrompt = `System/Instruction:\n${node.instruction}\n\n`;
287
+
288
+ if (node.dependsOn && node.dependsOn.length > 0) {
289
+ isolatedPrompt += `Context from previous steps:\n`;
290
+ for (const dep of deps) {
291
+ const previousResult = dep.response || "No data returned.";
292
+ if (!dep.schemaOnly) {
293
+ isolatedPrompt += `--- Response from ${dep.name} ---\n${previousResult}\n`;
294
+ }
295
+ if (dep.vars?.length) {
296
+ isolatedPrompt += `--- Vars from ${dep.name} with schema do not use {{vars.${dep.name}} ---\n`;
297
+ for (const varItem of dep.vars) {
298
+ isolatedPrompt += `--- ${varItem.name} ---\n${JSON.stringify(varItem.schema)}\n`;
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ runner.on("tool.start", tool => {
305
+ logger.info(`${node.nodeId}Tool started: ${tool.name}: ${JSON.stringify(tool.args)}`);
306
+ });
307
+ runner.on("tool.finish", tool => {
308
+ logger.info(`${node.nodeId} \n${JSON.stringify(tool)}`);
309
+ });
310
+
311
+ const response = await runner.handleUserMessage(isolatedPrompt);
312
+
313
+ const finalMessage = response.messages[response.messages.length - 1]?.content || "";
314
+ const responseInputTokens = response.token?.input || 0;
315
+ const responseOutputTokens = response.token?.output || 0;
316
+
317
+ nodeResults.set(node.nodeId, {
318
+ response: finalMessage,
319
+ token: {
320
+ input: responseInputTokens,
321
+ output: responseOutputTokens,
322
+ },
323
+ vars:
324
+ response.vars.map(([name, value]) => ({
325
+ name,
326
+ data: value.data,
327
+ schema: value.schema,
328
+ })) || [],
329
+ name: node.nodeId,
330
+ schemaOnly: node.schemaOnly,
331
+ });
332
+
333
+ tokenCount.input += responseInputTokens;
334
+ tokenCount.output += responseOutputTokens;
335
+ event.emit("token", {
336
+ input: responseInputTokens,
337
+ output: responseOutputTokens,
338
+ });
339
+ });
340
+
341
+ const results = await Promise.allSettled(phasePromises);
342
+
343
+ for (const result of results) {
344
+ if (result.status === "rejected") {
345
+ throw new Error(`Node execution failed: ${result.reason}`);
346
+ }
347
+ }
348
+ }
349
+
350
+ const phases = pQuery.plan.executionPhases || [];
351
+ let finalResponseText = "";
352
+
353
+ if (phases.length > 0) {
354
+ const lastPhase = phases[phases.length - 1];
355
+ const terminalNodeIds = lastPhase.parallelNodes.map(n => n.nodeId);
356
+ finalResponseText = terminalNodeIds
357
+ .map(id => nodeResults.get(id)?.response)
358
+ .filter(Boolean)
359
+ .join("\n\n");
360
+ }
361
+
362
+ event.emit("response", finalResponseText);
363
+ event.emit("status", "completed");
364
+
365
+ return {
366
+ result: finalResponseText,
367
+ token: tokenCount,
368
+ };
369
+ } catch (error: any) {
370
+ event.emit("status", "error");
371
+ event.emit("error", {
372
+ reason: error.message || "An unknown error occurred during execution",
373
+ });
374
+ return {
375
+ result: error.message || "An unknown error occurred during execution",
376
+ token: tokenCount,
377
+ };
378
+ }
379
+ };
380
+
381
+ static structureResponse = async <T extends AiProvider, Z extends z.ZodObject>(props: {
382
+ provider: T;
383
+ model: (typeof DefaultAiModels)[T][number]["id"];
384
+ credentials: AiInterfaceConstructor["credentials"];
385
+ message: string;
386
+ schema: Z;
387
+ title?: string;
388
+ description?: string;
389
+ }): Promise<{ data: z.infer<Z>; token: { input: number; output: number } }> => {
390
+ try {
391
+ const toolName = props.title || "structure_response";
392
+
393
+ const structureResponseTool = new ITool({
394
+ title: toolName,
395
+ description: props.description || "Structures a response based on a schema.",
396
+ schema: props.schema,
397
+ run: input => {
398
+ return [
399
+ { type: "text", text: JSON.stringify(input) },
400
+ {
401
+ type: "resource_link",
402
+ uri: "return",
403
+ name: "ResponseStructured",
404
+ _meta: { return: { isCompleted: true, isError: false } },
405
+ },
406
+ ];
407
+ },
408
+ });
409
+
410
+ const ai = this.client(props.provider)({
411
+ model: props.model,
412
+ credentials: props.credentials,
413
+ conversations: [],
414
+ preferences: {
415
+ name: "structurer",
416
+ type: "worker",
417
+ toolOnly: true,
418
+ temperature: 0.1,
419
+ },
420
+ whitelistedTools: [toolName],
421
+ });
422
+
423
+ ai.onEvery(e => {
424
+ console.log(e);
425
+ });
426
+
427
+ await ai.tools.attachTools({
428
+ type: "internal",
429
+ tool: [structureResponseTool],
430
+ isInternal: true,
431
+ });
432
+
433
+ const response = await ai.handleUserMessage(props.message);
434
+ const messages = response.messages;
435
+ console.log(JSON.stringify(messages));
436
+ const data = JSON.parse(messages[messages.length - 1].content) as z.infer<Z>;
437
+
438
+ return {
439
+ data,
440
+ token: response.token || { input: 0, output: 0 },
441
+ };
442
+ } catch (error) {
443
+ throw new Error(`Failed to structure response: ${error instanceof Error ? error.message : String(error)}`);
444
+ }
445
+ };
446
+ }
447
+
448
+ type BasePlannerProps<T extends AiProvider, Q extends AiProvider> = {
449
+ query: string;
450
+ systemMessage: string;
451
+ tools: ToolConnector[];
452
+ provider: T;
453
+ whitelist: string[];
454
+ model: (typeof DefaultAiModels)[T][number]["id"];
455
+ internal: Partial<TInternal>;
456
+ credentials: AiInterfaceConstructor["credentials"];
457
+ maxTokenPerExecution: {
458
+ input: number;
459
+ output: number;
460
+ };
461
+ planner: {
462
+ provider: Q;
463
+ model: (typeof DefaultAiModels)[Q][number]["id"];
464
+ credentials: AiInterfaceConstructor["credentials"];
465
+ maxToken?: {
466
+ input: number;
467
+ output: number;
468
+ };
469
+ };
470
+ };
471
+
472
+ type SummarizerProps<S extends AiProvider> =
473
+ | {
474
+ summarizer: {
475
+ provider: S;
476
+ model: (typeof DefaultAiModels)[S][number]["id"];
477
+ credentials: AiInterfaceConstructor["credentials"];
478
+ summarizeAt: {
479
+ input: number;
480
+ output: number;
481
+ };
482
+ };
483
+ }
484
+ | {
485
+ summarizer?: never;
486
+ };
487
+
488
+ type ExecuteWithPlannerProps<T extends AiProvider, S extends AiProvider> = BasePlannerProps<T, S> & SummarizerProps<S>;
489
+
490
+ type ExecuteWithPlannerEvents = {
491
+ token: {
492
+ input: number;
493
+ output: number;
494
+ };
495
+ status: "planning" | "summarizing" | "executing" | "completed" | "error" | "failed";
496
+ plan: {
497
+ title: string;
498
+ description: string;
499
+ queryClassification?: "direct_answer" | "requires_tools" | "unclear";
500
+ executionPhases:
501
+ | Array<{
502
+ phaseId: string;
503
+ parallelNodes: Array<{
504
+ nodeId: string;
505
+ schemaOnly: boolean;
506
+ instruction: string;
507
+ tools: string[];
508
+ dependsOn?: string[] | undefined;
509
+ }>;
510
+ }>
511
+ | undefined;
512
+ };
513
+ execution: string[][];
514
+ error: {
515
+ reason: string;
516
+ };
517
+ current: string;
518
+ response: string;
519
+ };
@@ -0,0 +1,3 @@
1
+ export * from "./providers/index.js";
2
+ export * from "./tools/index.js";
3
+ export * from "./aix.js";
@@ -0,0 +1,108 @@
1
+ import { InputData, jsonInputForTargetLanguage, quicktype } from "quicktype-core";
2
+
3
+ export class SchemaConvertor {
4
+ static async generateSchemaQT(input: unknown) {
5
+ try {
6
+ let parsedObj: unknown;
7
+
8
+ if (typeof input === "string") {
9
+ try {
10
+ parsedObj = JSON.parse(input);
11
+ } catch {
12
+ return {
13
+ success: false,
14
+ error: "Invalid JSON",
15
+ };
16
+ }
17
+
18
+ if (typeof parsedObj === "string")
19
+ return {
20
+ success: false,
21
+ error: "Invalid JSON",
22
+ };
23
+ } else {
24
+ parsedObj = input;
25
+ }
26
+
27
+ const sample = JSON.stringify(parsedObj);
28
+ const jsonInput = jsonInputForTargetLanguage("schema");
29
+
30
+ await jsonInput.addSource({
31
+ name: "Root",
32
+ samples: [sample],
33
+ });
34
+
35
+ const inputData = new InputData();
36
+ inputData.addInput(jsonInput);
37
+
38
+ const { lines } = await quicktype({
39
+ inputData,
40
+ lang: "schema",
41
+ rendererOptions: {
42
+ "just-types": "true",
43
+ "no-ref": "true",
44
+ "no-title": "true",
45
+ "top-level": "Root",
46
+ },
47
+ });
48
+
49
+ const schemaObj = JSON.parse(lines.join("\n"));
50
+ const squeezedSchemaString = JSON.stringify(schemaObj);
51
+
52
+ return {
53
+ success: true,
54
+ data: schemaObj as object,
55
+ squeezedString: squeezedSchemaString,
56
+ };
57
+ } catch (e: any) {
58
+ return {
59
+ success: false,
60
+ error: e.message,
61
+ };
62
+ }
63
+ }
64
+ static jsonToSchema(input: unknown): { type: string; schema: object } {
65
+ const parse = (data: unknown) => {
66
+ if (typeof data === "string") {
67
+ try {
68
+ const parsed = JSON.parse(data);
69
+ return typeof parsed === "string" ? parsed : parsed;
70
+ } catch {
71
+ return data;
72
+ }
73
+ }
74
+ return data;
75
+ };
76
+
77
+ const infer = (value: any): any => {
78
+ if (Array.isArray(value)) {
79
+ return {
80
+ type: "array",
81
+ items: value.length ? infer(value[0]) : {},
82
+ };
83
+ }
84
+
85
+ if (value === null) return { type: "null" };
86
+
87
+ if (typeof value === "object") {
88
+ const props: Record<string, any> = {};
89
+ for (const k in value) {
90
+ props[k] = infer(value[k]);
91
+ }
92
+ return {
93
+ type: "object",
94
+ properties: props,
95
+ };
96
+ }
97
+
98
+ return { type: typeof value };
99
+ };
100
+
101
+ const parsed = parse(input);
102
+ const schema = infer(parsed);
103
+ return {
104
+ type: schema.type,
105
+ schema,
106
+ };
107
+ }
108
+ }