startx 0.9.4 → 0.9.9

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 (48) hide show
  1. package/apps/cli/package.json +3 -1
  2. package/apps/cli/src/commands/common/hashing.ts +34 -0
  3. package/apps/cli/src/commands/common/ping.ts +11 -0
  4. package/apps/cli/src/commands/common/random.ts +41 -0
  5. package/apps/cli/src/commands/i-command.ts +6 -0
  6. package/apps/cli/src/commands/index.ts +6 -0
  7. package/apps/cli/src/index.ts +6 -4
  8. package/apps/cli/tsconfig.json +4 -6
  9. package/apps/startx-cli/dist/index.mjs +52 -52
  10. package/configs/eslint-config/src/configs/base.ts +26 -11
  11. package/package.json +2 -2
  12. package/packages/@db/drizzle/tsconfig.json +2 -8
  13. package/packages/@db/sqlite/tsconfig.json +2 -8
  14. package/packages/@repo/env/package.json +2 -1
  15. package/packages/@repo/env/src/utils.ts +7 -6
  16. package/packages/@repo/lib/src/events/i-event.ts +1 -1
  17. package/packages/aix/eslint.config.ts +4 -0
  18. package/packages/aix/package.json +53 -0
  19. package/packages/aix/src/aix.ts +519 -0
  20. package/packages/aix/src/index.ts +3 -0
  21. package/packages/aix/src/lib/convertor/schema-convertor.ts +108 -0
  22. package/packages/aix/src/lib/convertor/variable-resolver.ts +161 -0
  23. package/packages/aix/src/lib/tokenizer/index.ts +1 -0
  24. package/packages/aix/src/lib/tokenizer/tokenizer.ts +42 -0
  25. package/packages/aix/src/providers/ai-chat.ts +25 -0
  26. package/packages/aix/src/providers/ai-event.ts +21 -0
  27. package/packages/aix/src/providers/ai-interface.ts +236 -0
  28. package/packages/aix/src/providers/ai-prompt.ts +14 -0
  29. package/packages/aix/src/providers/default-models.ts +471 -0
  30. package/packages/aix/src/providers/index.ts +1 -0
  31. package/packages/aix/src/providers/openai/openai.ts +139 -0
  32. package/packages/aix/src/providers/providers.ts +39 -0
  33. package/packages/aix/src/providers/types.ts +54 -0
  34. package/packages/aix/src/tools/generic/database.ts +290 -0
  35. package/packages/aix/src/tools/generic/forecast.ts +216 -0
  36. package/packages/aix/src/tools/generic/index.ts +4 -0
  37. package/packages/aix/src/tools/generic/planner.ts +114 -0
  38. package/packages/aix/src/tools/generic/summarizer.ts +101 -0
  39. package/packages/aix/src/tools/i-tool.ts +33 -0
  40. package/packages/aix/src/tools/index.ts +2 -0
  41. package/packages/aix/src/tools/system/index.ts +297 -0
  42. package/packages/aix/src/tools/tool-manager.ts +241 -0
  43. package/packages/aix/src/tools/types.ts +109 -0
  44. package/packages/aix/tsconfig.json +7 -0
  45. package/packages/aix/vitest.config.ts +3 -0
  46. package/pnpm-workspace.yaml +7 -0
  47. package/turbo.json +0 -1
  48. package/apps/cli/src/commands/ping.ts +0 -10
@@ -0,0 +1,161 @@
1
+ import { getQuickJS } from "quickjs-emscripten";
2
+
3
+ export class VariableResolver {
4
+ static extractPlaceholders(text: string): string[] {
5
+ const matches = [...text.matchAll(/{{(.*?)}}/g)];
6
+ return matches.map(m => m[1]?.trim() || "");
7
+ }
8
+
9
+ static async evaluateInQuickJS(expression: string, context: Record<string, any>): Promise<any> {
10
+ const qjs = await getQuickJS();
11
+ const vm = qjs.newContext();
12
+
13
+ try {
14
+ const global = vm.global;
15
+
16
+ for (const scopeKey of Object.keys(context)) {
17
+ const scopeObject = vm.newObject();
18
+
19
+ for (const varKey of Object.keys(context?.[scopeKey])) {
20
+ const value = context?.[scopeKey]?.[varKey];
21
+ let valueHandle;
22
+
23
+ if (typeof value === "function") {
24
+ const fnCode = `(${value.toString()})`;
25
+ const fnEval = vm.evalCode(fnCode);
26
+ if (fnEval.error) {
27
+ console.warn(`Function eval failed: ${fnCode}`);
28
+ continue;
29
+ }
30
+ valueHandle = fnEval.value;
31
+ } else {
32
+ const json = value instanceof Date ? `"${value.toISOString()}"` : JSON.stringify(value);
33
+ const valEval = vm.evalCode(json);
34
+ if (valEval.error) {
35
+ console.warn(`Value eval failed: ${json}`);
36
+ continue;
37
+ }
38
+ valueHandle = valEval.value;
39
+ }
40
+
41
+ vm.setProp(scopeObject, varKey, valueHandle);
42
+ valueHandle.dispose();
43
+ }
44
+
45
+ vm.setProp(global, scopeKey, scopeObject);
46
+ scopeObject.dispose();
47
+ }
48
+
49
+ const wrappedExpr = `
50
+ (function() {
51
+ let val = ${expression};
52
+ while (typeof val === 'function') {
53
+ val = val();
54
+ }
55
+ return val;
56
+ })()
57
+ `;
58
+ const resultEval = vm.evalCode(wrappedExpr);
59
+ if (resultEval.error) {
60
+ console.warn(`Expression eval failed:`, resultEval.error);
61
+ return expression;
62
+ }
63
+
64
+ const result = vm.dump(resultEval.value);
65
+ resultEval.value.dispose();
66
+ return result;
67
+ } catch (error) {
68
+ console.warn("QuickJS error:", error);
69
+ return expression;
70
+ } finally {
71
+ vm.dispose();
72
+ }
73
+ }
74
+ static async resolveText(text: string, context: Record<string, any>): Promise<string> {
75
+ const placeholders = this.extractPlaceholders(text);
76
+
77
+ const replacements = await Promise.all(
78
+ placeholders.map(async expr => {
79
+ try {
80
+ const result = await this.evaluateInQuickJS(expr, context);
81
+ return {
82
+ expr,
83
+ result,
84
+ valid: result !== expr && result !== undefined,
85
+ };
86
+ } catch (e) {
87
+ return { expr, result: "", valid: false };
88
+ }
89
+ })
90
+ );
91
+
92
+ let resolved = text;
93
+ for (const { expr, result, valid } of replacements) {
94
+ if (valid) {
95
+ resolved = resolved.replace(`{{${expr}}}`, String(result));
96
+ }
97
+ }
98
+ return resolved;
99
+ }
100
+
101
+ static async resolveMessageVariables({
102
+ messages,
103
+ variables: variablesFromDb,
104
+ }: {
105
+ messages: Array<{ content: string; role: string; timestamp?: Date | null }>;
106
+ variables: Array<{
107
+ key: string;
108
+ type: "string" | "number" | "boolean" | "object" | "array" | "fn";
109
+ data: string;
110
+ access: "user" | "agent" | "system" | "message";
111
+ }>;
112
+ }) {
113
+ const messageVars = messages.flatMap(m => this.extractPlaceholders(m.content));
114
+ const allExpressions = [...new Set([...messageVars])];
115
+ const rootKeys = new Set(allExpressions.map(e => e.split(".")[0]));
116
+
117
+ const context: Record<string, any> = {
118
+ user: {},
119
+ agent: {},
120
+ system: {},
121
+ message: {},
122
+ };
123
+
124
+ for (const v of variablesFromDb) {
125
+ if (!rootKeys.has(v.access)) continue;
126
+
127
+ try {
128
+ let parsed: any;
129
+ if (v.type === "fn") {
130
+ parsed = eval(`(function() {
131
+ return (function() {
132
+ ${v.data}
133
+ });
134
+ })()`);
135
+ } else if (v.type === "object" || v.type === "array") {
136
+ parsed = JSON.parse(v.data);
137
+ } else if (v.type === "number") {
138
+ parsed = Number(v.data);
139
+ } else if (v.type === "boolean") {
140
+ parsed = v.data === "true";
141
+ } else {
142
+ parsed = v.data;
143
+ }
144
+
145
+ if (!context[v.access]) context[v.access] = {};
146
+ context[v.access][v.key] = parsed;
147
+ } catch (e) {
148
+ console.warn(`Failed to parse variable ${v.key}`, e);
149
+ }
150
+ }
151
+
152
+ const resolvedMessages = await Promise.all(
153
+ messages.map(async msg => ({
154
+ ...msg,
155
+ content: await this.resolveText(msg.content, context),
156
+ }))
157
+ );
158
+
159
+ return resolvedMessages;
160
+ }
161
+ }
@@ -0,0 +1 @@
1
+ export * from "./tokenizer.js";
@@ -0,0 +1,42 @@
1
+ import { getEncoding } from "js-tiktoken";
2
+
3
+ // We get the encoding for a specific model, 'cl100k_base' is used by gpt-4, gpt-3.5-turbo, etc.
4
+ // It's best practice to instantiate the encoder once and reuse it.
5
+ const enc = getEncoding("cl100k_base");
6
+
7
+ export class Tokenizer {
8
+ /**
9
+ * Returns the total number of tokens in the text.
10
+ * @param text The string to count tokens for.
11
+ * @returns The number of tokens.
12
+ */
13
+ static count(text: string): number {
14
+ return enc.encode(text).length;
15
+ }
16
+
17
+ /**
18
+ * Checks if the text is within a given token limit.
19
+ * @param text The string to check.
20
+ * @param limit The token limit (defaults to 4096).
21
+ * @returns True if the token count is within the limit, false otherwise.
22
+ */
23
+ static isWithinLimit(text: string, limit: number = 4096): boolean {
24
+ // Note: We encode and check length instead of calling count()
25
+ // to avoid encoding the text twice if we needed the tokens later.
26
+ // If you only need a boolean, Tokenizer.count(text) <= limit is fine.
27
+ if (!text) return true;
28
+ const tokens = enc.encode(text);
29
+ return tokens.length <= limit;
30
+ }
31
+
32
+ /**
33
+ * Returns the array of token integers.
34
+ * @param text The string to tokenize.
35
+ * @returns An array of token numbers.
36
+ */
37
+ static getTokens(text: string): number[] {
38
+ const tokens = enc.encode(text);
39
+ // The result from encode() is a Uint32Array, so we convert it to a standard number array.
40
+ return Array.from(tokens);
41
+ }
42
+ }
@@ -0,0 +1,25 @@
1
+ import type { AiChatMessage, AiResource } from "./types.js";
2
+
3
+ export class AiChat {
4
+ private messages: AiChatMessage[];
5
+ private currentMessage: string;
6
+ private resources: AiResource[] = [];
7
+ constructor(msgs?: AiChatMessage[]) {
8
+ this.messages = msgs || [];
9
+ this.currentMessage = "";
10
+ }
11
+ public getMessages() {
12
+ return this.messages;
13
+ }
14
+
15
+ public getResources() {
16
+ return this.resources;
17
+ }
18
+ public addMessage(msg: AiChatMessage) {
19
+ if (!this.currentMessage) this.currentMessage = msg.content;
20
+ this.messages.push(msg);
21
+ }
22
+ public addResource(resource: AiResource[]) {
23
+ this.resources.push(...resource);
24
+ }
25
+ }
@@ -0,0 +1,21 @@
1
+ import type { ToolReturn } from "../tools/types.js";
2
+
3
+ export type AiEventType = {
4
+ "ai.think.partial": string;
5
+ "ai.finish": string;
6
+ "tool.start": {
7
+ name: string;
8
+ args: any;
9
+ };
10
+ "tool.finish": ToolReturn;
11
+ "log": {
12
+ type: "info" | "warn" | "error";
13
+ message: string;
14
+ stack?: string;
15
+ meta?: Record<string, unknown>;
16
+ };
17
+ "token": {
18
+ input: number;
19
+ output: number;
20
+ };
21
+ };
@@ -0,0 +1,236 @@
1
+ import { IEvent, type IEventWithPayload } from "@repo/lib/events";
2
+ import { encode } from "@toon-format/toon";
3
+ import { nanoid } from "nanoid";
4
+ import type z from "zod";
5
+
6
+ import { AiChat } from "./ai-chat.js";
7
+ import type { AiEventType } from "./ai-event.js";
8
+ import { AiPrompt } from "./ai-prompt.js";
9
+ import type { DefaultAiModels } from "./default-models.js";
10
+ import type { AiChatMessage, AiProvider } from "./types.js";
11
+ import { SchemaConvertor } from "../lib/convertor/schema-convertor.js";
12
+ import { Tokenizer } from "../lib/tokenizer/tokenizer.js";
13
+ import { ToolManager } from "../tools/tool-manager.js";
14
+ import type { GenericTool, TInternal } from "../tools/types.js";
15
+ export interface AiInterfaceConstructor<T extends AiProvider = AiProvider> {
16
+ preferences: {
17
+ name: string;
18
+ version?: string;
19
+ type: "manager" | "worker";
20
+ toolOnly: boolean;
21
+ temperature?: number;
22
+ seed?: number;
23
+ };
24
+ whitelistedTools?: string[];
25
+ model: (typeof DefaultAiModels)[T][number]["id"];
26
+ conversations: AiChatMessage[];
27
+ credentials: {
28
+ apiKey: string;
29
+ baseUrl?: string;
30
+ };
31
+ tokens?: Partial<Record<"maxLimit" | "current" | "total", { input: number; output: number }>> & {
32
+ minTokenToGenerateSchemaOnToolCall?: number;
33
+ };
34
+ internal?: TInternal;
35
+ }
36
+ export abstract class AiInterface<AI, P extends AiProvider> {
37
+ constructor(props: AiInterfaceConstructor) {
38
+ this.preferences = props.preferences;
39
+ this.tools = new ToolManager({ whitelist: props.whitelistedTools ?? [], events: this.event });
40
+ this.chats = new AiChat(props.conversations);
41
+ this.model = props.model;
42
+ this.credentials = props.credentials;
43
+ this.iInternal = props.internal;
44
+ const defaultToken = { input: 0, output: 0 };
45
+ const defaultMaxToken = { input: 20000, output: 4000 };
46
+ this.token = {
47
+ total: props.tokens?.total ?? defaultToken,
48
+ current: props.tokens?.current ?? defaultToken,
49
+ maxLimit: props.tokens?.maxLimit ?? defaultMaxToken,
50
+ minTokenToGenerateSchemaOnToolCall: props.tokens?.minTokenToGenerateSchemaOnToolCall ?? 1000,
51
+ };
52
+ }
53
+ abstract ai: AI;
54
+ protected abstract handleAi(): Promise<void>;
55
+ abstract listModels(): Promise<{
56
+ provider: string;
57
+ name: string;
58
+ }>;
59
+ protected model: (typeof DefaultAiModels)[P][number]["id"];
60
+ protected credentials: AiInterfaceConstructor["credentials"];
61
+ private iInternal: TInternal;
62
+ protected chats: AiChat;
63
+ public token: Required<AiInterfaceConstructor["tokens"]>;
64
+ public preferences: AiInterfaceConstructor["preferences"];
65
+ public tools: ToolManager;
66
+
67
+ protected callTool = async (props: {
68
+ name: string;
69
+ args: z.infer<GenericTool["schema"]>;
70
+ toolCallId?: string;
71
+ variablePlaceholders?: string[];
72
+ }) => {
73
+ // this.chats.addMessage({
74
+ // role: "assistant",
75
+ // content: `Calling tool ${props.name} with args ${JSON.stringify(props.args)}`,
76
+ // timestamp: new Date(),
77
+ // });
78
+
79
+ const res = await this.tools.callTool({
80
+ name: props.name,
81
+ args: props.args,
82
+ toolId: props.toolCallId ?? nanoid(),
83
+ internal: this.iInternal,
84
+ });
85
+
86
+ const usedVariables: string[] = props.variablePlaceholders ?? [];
87
+ this.chats.addResource(res.resources);
88
+ for (const e of res.content) {
89
+ switch (e.role) {
90
+ case "assistant":
91
+ case "system":
92
+ case "user": {
93
+ this.chats.addMessage(e);
94
+ break;
95
+ }
96
+ case "tool": {
97
+ const pString = e.content;
98
+ let data: object | undefined = undefined;
99
+ try {
100
+ data = JSON.parse(e.content) as object;
101
+ } catch {}
102
+ if (!data || res.isError || (!this.iInternal?.system?.schemaOnly && res.isCompleted)) {
103
+ this.chats.addMessage({
104
+ role: "tool",
105
+ content: AiPrompt.tool.call.success({
106
+ toolName: props.name,
107
+ isCompleted: res.isCompleted,
108
+ isError: res.isError,
109
+ args: JSON.stringify(props.args),
110
+ data: pString,
111
+ }),
112
+ tool_call_id: e.tool_call_id,
113
+ timestamp: new Date(),
114
+ });
115
+ continue;
116
+ }
117
+
118
+ const toon = encode(data);
119
+ const token = Tokenizer.count(toon);
120
+
121
+ if (token < (this.token?.minTokenToGenerateSchemaOnToolCall ?? 1000) && !this.iInternal?.system?.schemaOnly) {
122
+ this.chats.addMessage({
123
+ role: "tool",
124
+ content: AiPrompt.tool.call.success({
125
+ toolName: props.name,
126
+ isCompleted: res.isCompleted,
127
+ isError: res.isError,
128
+ args: JSON.stringify(props.args),
129
+ data: toon,
130
+ }),
131
+ tool_call_id: e.tool_call_id,
132
+ timestamp: new Date(),
133
+ });
134
+ continue;
135
+ }
136
+
137
+ const schema = SchemaConvertor.jsonToSchema(e.content);
138
+
139
+ if (!schema.schema) {
140
+ this.chats.addMessage({
141
+ role: "tool",
142
+ content: AiPrompt.tool.call.success({
143
+ toolName: props.name,
144
+ isCompleted: res.isCompleted,
145
+ isError: true,
146
+ args: JSON.stringify(props.args),
147
+ data: `Schema generation failed:`,
148
+ }),
149
+ tool_call_id: e.tool_call_id,
150
+ timestamp: new Date(),
151
+ });
152
+ continue;
153
+ }
154
+ let value = usedVariables?.[0] ?? `v_${nanoid().replace(/[^a-zA-Z0-9_]/g, "_")}`;
155
+ if (/^[0-9]/.test(value)) {
156
+ value = "_" + value;
157
+ }
158
+ usedVariables.pop();
159
+ this.iInternal?.vars?.set(value, {
160
+ data: JSON.stringify(data),
161
+ schema: schema.schema,
162
+ });
163
+ this.chats.addMessage({
164
+ role: "tool",
165
+ content: AiPrompt.tool.call.successSchema({
166
+ args: JSON.stringify(props.args),
167
+ toolName: props.name,
168
+ varName: `vars.${value}`,
169
+ schema: JSON.stringify(schema.schema),
170
+ }),
171
+ tool_call_id: e.tool_call_id,
172
+ timestamp: new Date(),
173
+ });
174
+ if (
175
+ !this.tools.whitelist.find(e => e === "execute_javascript") &&
176
+ this.tools.registered.has("execute_javascript")
177
+ ) {
178
+ this.event.emit("log", {
179
+ type: "info",
180
+ message: `Tool execute_javascript is not whitelisted. Adding...`,
181
+ });
182
+
183
+ this.tools.whitelist.push("execute_javascript");
184
+ this.tools.tools.set("execute_javascript", this.tools.registered.get("execute_javascript")!);
185
+ }
186
+ break;
187
+ }
188
+ }
189
+ }
190
+ return {
191
+ isCompleted: res.isCompleted,
192
+ isError: res.isError,
193
+ };
194
+ };
195
+ private async syncModels() {
196
+ // const models = await this.ai.
197
+ }
198
+ // Events
199
+ protected event: IEvent<AiEventType> = new IEvent();
200
+ public on<K extends keyof AiEventType>(event: K, handler: (payload: AiEventType[K]) => void) {
201
+ this.event.on(event, handler);
202
+ return () => this.event.off(event, handler);
203
+ }
204
+ public onEvery(handler: (e: IEventWithPayload<AiEventType>) => void) {
205
+ return this.event.onEvery(handler);
206
+ }
207
+ public async handleUserMessage(message: string) {
208
+ this.chats.addMessage({
209
+ role: "user",
210
+ content: message ?? "",
211
+ timestamp: new Date(),
212
+ });
213
+ this.event.on("token", ({ input, output }) => {
214
+ this.token!.total.input += input;
215
+ this.token!.total.output += output;
216
+
217
+ this.token!.current.input += input;
218
+ this.token!.current.output += output;
219
+
220
+ if (this.token!.total.input > this.token!.maxLimit.input) {
221
+ throw new Error("Token input limit exceeded");
222
+ }
223
+
224
+ if (this.token!.total.output > this.token!.maxLimit.output) {
225
+ throw new Error("Token output limit exceeded");
226
+ }
227
+ });
228
+ await this.handleAi();
229
+ return {
230
+ token: this.token!.total,
231
+ messages: this.chats.getMessages(),
232
+ resources: this.chats.getResources(),
233
+ vars: Array.from(this.iInternal?.vars.entries() || []),
234
+ };
235
+ }
236
+ }
@@ -0,0 +1,14 @@
1
+ export const AiPrompt = {
2
+ tool: {
3
+ call: {
4
+ success: (props: { toolName: string; args: string; isCompleted?: boolean; data: string; isError?: boolean }) =>
5
+ props.isError
6
+ ? `${props.toolName} with args ${props.args} returned ERROR ${props.data}`
7
+ : props.isCompleted
8
+ ? props.data
9
+ : `Tool ${props.toolName} with args ${props.args} returned ${props.data}`,
10
+ successSchema: (props: { toolName: string; args: string; varName: string; schema: string }) =>
11
+ `Tool call ${props.toolName} with args ${JSON.stringify(props.args)} responded with {{${props.varName}}\n\nSchema: ${JSON.stringify(props.schema)}\n\nCRITICAL INSTRUCTION: You MUST use the 'execute_javascript' tool to process this data. You must write valid JavaScript code with a 'return' statement.`,
12
+ },
13
+ },
14
+ };