@channela9/mogi 0.1.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.
@@ -0,0 +1,28 @@
1
+ name: Publish to npm
2
+ on:
3
+ release:
4
+ types: [published]
5
+ # Alternatively, trigger on a tag push:
6
+ # push:
7
+ # tags:
8
+ # - 'v*'
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Setup Node.js
17
+ uses: actions/setup-node@v4
18
+ with:
19
+ node-version: 20
20
+ registry-url: https://registry.npmjs.org/
21
+
22
+ - name: Install Dependencies
23
+ run: npm ci
24
+
25
+ - name: Publish Package
26
+ run: npm publish
27
+ env:
28
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shaun Colegado
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # Mogi
2
+
3
+ Mogi is a simulation framework that allows you to create and run complex agent-based simulations using AI-driven nodes and processes. This README provides an overview of how to set up and use Mogi, including a detailed example of an amusement park simulation.
4
+
5
+ ## Installation
6
+
7
+ To install Mogi, clone the repository and install the dependencies:
8
+
9
+ ```bash
10
+ git clone https://github.com/yourusername/mogi.git
11
+ cd mogi
12
+ npm install
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ### Creating a Simulation
18
+
19
+ To create a simulation, you need to initialize the `MogiSimulation` class with a configuration object. You can then add agents and define processes and nodes that the agents will go through.
20
+
21
+ ### Example: Amusement Park Simulation
22
+
23
+ Below is an example of how to set up an amusement park simulation where agents (riders) go through ticketing, ride, and exit nodes.
24
+
25
+ #### Step 1: Initialize AI Service
26
+
27
+ ```typescript
28
+ import { GoogleAIService } from "./ai-interfaces/ai-google";
29
+
30
+ const AI = new GoogleAIService("YOUR_API_KEY", "gemini-2.0-flash-exp");
31
+ ```
32
+
33
+ #### Step 2: Create Simulation Instance
34
+
35
+ ```typescript
36
+ import { MogiSimulation } from "./mogi";
37
+
38
+ const simulation = new MogiSimulation({
39
+ description: "Amusement Park",
40
+ delayMs: 3000,
41
+ maxConcurrency: 2,
42
+ printLogs: true,
43
+ });
44
+ ```
45
+
46
+ #### Step 3: Add Agents
47
+
48
+ ```typescript
49
+ const rider1Id = simulation.addAgent({
50
+ attributes: { name: "Jake", money: 10, happiness: 5, height: 140, fatigue: 0 },
51
+ }, "Jake");
52
+
53
+ const rider2Id = simulation.addAgent({
54
+ attributes: { name: "John", money: 10, happiness: 5, height: 180, fatigue: 0 },
55
+ }, "John");
56
+ ```
57
+
58
+ #### Step 4: Create Nodes
59
+
60
+ ```typescript
61
+ import { createMogiNode } from "./mogi";
62
+ import { SchemaType } from "@google/generative-ai";
63
+
64
+ const ticketingNode = createMogiNode(
65
+ 'ticketBox',
66
+ AI,
67
+ `
68
+ As ticketing staff:
69
+ - Verify rider height (150-200cm)
70
+ - Charge $2 if valid
71
+ - Update worker fatigue (+1)
72
+ - Update rider money and happiness
73
+ `,
74
+ {
75
+ ticket: {
76
+ type: SchemaType.STRING,
77
+ enum: ["standard", "premium"],
78
+ },
79
+ money: {
80
+ type: SchemaType.NUMBER,
81
+ }
82
+ },
83
+ { useChainOfThought: true }
84
+ );
85
+
86
+ const rideNode = createMogiNode(
87
+ 'rideOperator',
88
+ AI,
89
+ `
90
+ As ride operator:
91
+ - Simulate ride experience
92
+ - Increase rider happiness (1-5 points)
93
+ - Increase rider fatigue (1-3 points)
94
+ `,
95
+ {
96
+ happiness: { type: SchemaType.NUMBER },
97
+ fatigue: { type: SchemaType.NUMBER },
98
+ },
99
+ { useChainOfThought: false }
100
+ );
101
+
102
+ const exitNode = createMogiNode(
103
+ 'exitStaff',
104
+ AI,
105
+ `
106
+ As exit staff:
107
+ - Give farewell greeting
108
+ - Final happiness adjustment (±1)
109
+ `,
110
+ {
111
+ happiness: { type: SchemaType.NUMBER },
112
+ fatigue: { type: SchemaType.NUMBER },
113
+ },
114
+ { useChainOfThought: false }
115
+ );
116
+ ```
117
+
118
+ #### Step 5: Create Processes
119
+
120
+ ```typescript
121
+ import { MogiProcess } from "./mogi";
122
+
123
+ const fullRideProcess = new MogiProcess("full-ride")
124
+ .addNode(ticketingNode)
125
+ .addNode(rideNode)
126
+ .addNode(exitNode);
127
+
128
+ const shortProcess = new MogiProcess("short-exit").addNode(exitNode);
129
+ ```
130
+
131
+ #### Step 6: Create Conditional Process
132
+
133
+ ```typescript
134
+ import { MogiConditionalProcess } from "./mogi";
135
+
136
+ const heightCondition = (min: number, max: number) => {
137
+ return (agent: MogiAgentState) => {
138
+ const height = agent.attributes.height;
139
+ if (height) return height >= min && height <= max;
140
+ return false;
141
+ };
142
+ };
143
+
144
+ const amusementParkFlow = new MogiConditionalProcess(
145
+ "height-check-flow",
146
+ heightCondition(150, 200),
147
+ fullRideProcess,
148
+ shortProcess
149
+ );
150
+ ```
151
+
152
+ #### Step 7: Run Simulation
153
+
154
+ ```typescript
155
+ simulation.runProcess(amusementParkFlow).then(() => {
156
+ console.log("Simulation completed\n");
157
+
158
+ // Helper to display agent history
159
+ const logHistory = (agentId: string) => {
160
+ const agent = simulation.getAgentState(agentId);
161
+ console.log(`\n${agent.attributes.name}'s journey:`);
162
+ agent.history.forEach((entry) => {
163
+ console.log(`[${entry.timestamp.toLocaleTimeString()}] ${JSON.stringify(entry.changes)}`);
164
+ });
165
+ console.log(agent.attributes);
166
+ };
167
+
168
+ logHistory(rider1Id);
169
+ logHistory(rider2Id);
170
+
171
+ const usage = simulation.getAIUsage();
172
+ console.log(`API Usage:
173
+ Calls: ${usage.totalCalls}
174
+ Input tokens: ${usage.totalInputTokens}
175
+ Output tokens: ${usage.totalOutputTokens}
176
+ Estimated cost: $${usage.estimatedCost.toFixed(4)}`);
177
+ });
178
+ ```
179
+
180
+ ## License
181
+
182
+ This project is licensed under the MIT License.
@@ -0,0 +1,96 @@
1
+ import { Schema } from "@google/generative-ai";
2
+
3
+ export type AIPromptResponse = string | string[];
4
+ export type TokenUsage = { input: number; output: number };
5
+
6
+ export interface AIUsageStats {
7
+ totalCalls: number;
8
+ totalInputTokens: number;
9
+ totalOutputTokens: number;
10
+ estimatedCost: number;
11
+ }
12
+
13
+ export type AIInterfaceModelsDefinition = {
14
+ primary: string;
15
+ utility: string;
16
+ };
17
+
18
+ /**
19
+ * Abstract class representing an AI service.
20
+ * All AI services should implement as singletons.
21
+ */
22
+ export abstract class AIService {
23
+ protected abstract usageStats: AIUsageStats;
24
+ static instance: AIService;
25
+ /**
26
+ * Creates a schema from an object.
27
+ * @param object - The object to create a schema from.
28
+ * @returns The created schema.
29
+ */
30
+ abstract createSchema(object: object): Schema;
31
+ /**
32
+ * Prompts the AI service with a system message, content, and instruction.
33
+ * @param system - The system message.
34
+ * @param content - The content to process.
35
+ * @param instruction - The instruction for the AI.
36
+ * @param schema - The schema for the response.
37
+ * @returns The AI prompt response.
38
+ */
39
+ abstract prompt(
40
+ system: string,
41
+ content: string,
42
+ instruction: string,
43
+ schema?: Schema
44
+ ): Promise<AIPromptResponse>;
45
+ /**
46
+ * Prompts the AI service with a system message, content, and instruction, and generates reasoning.
47
+ * @param system - The system message.
48
+ * @param content - The content to process.
49
+ * @param instruction - The instruction for the AI.
50
+ * @param schema - The schema for the response.
51
+ * @returns The AI prompt response with reasoning.
52
+ */
53
+ abstract promptThinking(
54
+ system: string,
55
+ content: string,
56
+ instruction: string,
57
+ schema?: Schema
58
+ ): Promise<AIPromptResponse>;
59
+ /**
60
+ * Sets the primary model for the AI service.
61
+ * @param model - The primary model.
62
+ */
63
+ abstract setPrimaryModel(model: string): void;
64
+ /**
65
+ * Sets the utility model for the AI service.
66
+ * @param model - The utility model.
67
+ */
68
+ abstract setUtilityModel(model: string): void;
69
+ /**
70
+ * Sets the generation configuration for the AI service.
71
+ * @param config - The generation configuration.
72
+ */
73
+ abstract setGenerationConfig(config: object): void;
74
+ /**
75
+ * Calculates the cost of the AI service based on input and output tokens.
76
+ * @param inputTokens - The number of input tokens.
77
+ * @param outputTokens - The number of output tokens.
78
+ * @returns The calculated cost.
79
+ */
80
+ abstract calculateCost(inputTokens: number, outputTokens: number): number;
81
+ /**
82
+ * Sets the pricing for the AI service.
83
+ * @param input - The cost per million input tokens.
84
+ * @param output - The cost per million output tokens.
85
+ */
86
+ abstract setPricing(input: number, output: number): void;
87
+ /**
88
+ * Gets the usage statistics for the AI service.
89
+ * @returns The usage statistics.
90
+ */
91
+ getUsageStats(): AIUsageStats;
92
+ /**
93
+ * Resets the usage statistics for the AI service.
94
+ */
95
+ resetUsageStats(): void;
96
+ }
@@ -0,0 +1,53 @@
1
+ // ai.ts - Universal AI Interface
2
+ import { SchemaType, Schema } from "@google/generative-ai";
3
+
4
+ export type AIPromptResponse = string | string[];
5
+ export type TokenUsage = { input: number; output: number };
6
+
7
+ export interface AIUsageStats {
8
+ totalCalls: number;
9
+ totalInputTokens: number;
10
+ totalOutputTokens: number;
11
+ estimatedCost: number;
12
+ }
13
+
14
+ export type AIInterfaceModelsDefinition = {
15
+ primary: string;
16
+ utility: string;
17
+ };
18
+
19
+ // all AI services should implement as singletons
20
+ export abstract class AIService {
21
+ protected abstract usageStats: AIUsageStats;
22
+ static instance: AIService;
23
+ abstract createSchema(object: object): Schema;
24
+ abstract prompt(
25
+ system: string,
26
+ content: string,
27
+ instruction: string,
28
+ schema?: Schema
29
+ ): Promise<AIPromptResponse>;
30
+ abstract promptThinking(
31
+ system: string,
32
+ content: string,
33
+ instruction: string,
34
+ schema?: Schema
35
+ ): Promise<AIPromptResponse>;
36
+ abstract setPrimaryModel(model: string): void;
37
+ abstract setUtilityModel(model: string): void;
38
+ abstract setGenerationConfig(config: object): void;
39
+ abstract calculateCost(inputTokens: number, outputTokens: number): number
40
+ abstract setPricing(input: number, output: number): void;
41
+ getUsageStats(): AIUsageStats {
42
+ return this.usageStats;
43
+ }
44
+
45
+ resetUsageStats(): void {
46
+ this.usageStats = {
47
+ totalCalls: 0,
48
+ totalInputTokens: 0,
49
+ totalOutputTokens: 0,
50
+ estimatedCost: 0,
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,214 @@
1
+ // ai/gemini.ts
2
+ import {
3
+ GoogleGenerativeAI,
4
+ HarmCategory,
5
+ HarmBlockThreshold,
6
+ type GenerationConfig,
7
+ type SafetySetting,
8
+ type Schema,
9
+ SchemaType,
10
+ GenerateContentResponse,
11
+ } from "@google/generative-ai";
12
+ import { AIService, AIPromptResponse } from "../ai-interface";
13
+
14
+ const SAFETY_SETTINGS: SafetySetting[] = [
15
+ {
16
+ category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
17
+ threshold: HarmBlockThreshold.BLOCK_NONE,
18
+ },
19
+ {
20
+ category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
21
+ threshold: HarmBlockThreshold.BLOCK_NONE,
22
+ },
23
+ {
24
+ category: HarmCategory.HARM_CATEGORY_HARASSMENT,
25
+ threshold: HarmBlockThreshold.BLOCK_NONE,
26
+ },
27
+ {
28
+ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
29
+ threshold: HarmBlockThreshold.BLOCK_NONE,
30
+ },
31
+ ];
32
+
33
+ const DEFAULT_CONFIG: GenerationConfig = {
34
+ temperature: 0.1,
35
+ topP: 1,
36
+ topK: 1,
37
+ frequencyPenalty: 0.5,
38
+ };
39
+
40
+ export class GoogleAIService extends AIService {
41
+ static instance: GoogleAIService;
42
+
43
+ protected usageStats = {
44
+ totalCalls: 0,
45
+ totalInputTokens: 0,
46
+ totalOutputTokens: 0,
47
+ estimatedCost: 0,
48
+ };
49
+
50
+ private genAI: GoogleGenerativeAI;
51
+ private models = {
52
+ primary: "gemini-1.5-flash",
53
+ utility: "gemini-1.5-flash",
54
+ };
55
+ private config: GenerationConfig = DEFAULT_CONFIG;
56
+ private modelPricing = {
57
+ inputCostPerMToken: 0.15,
58
+ outputCostPerMToken: 0.60, // pessimistic pricing calculation. highest pricing bracket for Gemini 1.5 Pro >128k token prompts
59
+ };
60
+
61
+ constructor(apiKey: string, model: string, subModel?: string) {
62
+ super();
63
+
64
+ if (GoogleAIService.instance) {
65
+ return GoogleAIService.instance;
66
+ }
67
+
68
+ this.genAI = new GoogleGenerativeAI(apiKey);
69
+
70
+ this.models.primary = model;
71
+ if (subModel) {
72
+ this.models.utility = subModel;
73
+ } else {
74
+ this.models.utility = model;
75
+ }
76
+
77
+ GoogleAIService.instance = this;
78
+ }
79
+
80
+ createSchema(obj: object): Schema {
81
+ const properties: Record<string, Schema> = {};
82
+ for (const [key, value] of Object.entries(obj)) {
83
+ properties[key] = this.inferSchema(value);
84
+ }
85
+ return { type: SchemaType.OBJECT, properties, nullable: false };
86
+ }
87
+
88
+ private inferSchema(value: unknown): Schema {
89
+ switch (typeof value) {
90
+ case "string":
91
+ return { type: SchemaType.STRING };
92
+ case "number":
93
+ return { type: SchemaType.NUMBER };
94
+ case "boolean":
95
+ return { type: SchemaType.BOOLEAN };
96
+ case "object":
97
+ if (Array.isArray(value)) {
98
+ return {
99
+ type: SchemaType.ARRAY,
100
+ items: value.length > 0 ? this.inferSchema(value[0]) : { type: SchemaType.STRING },
101
+ };
102
+ }
103
+ return value ? this.createSchema(value) : { type: SchemaType.OBJECT };
104
+ default:
105
+ return { type: SchemaType.STRING };
106
+ }
107
+ }
108
+
109
+ async prompt(
110
+ system: string,
111
+ content: string,
112
+ instruction: string,
113
+ schema?: Schema
114
+ ): Promise<AIPromptResponse> {
115
+ const model = this.genAI.getGenerativeModel({
116
+ model: this.models.primary,
117
+ generationConfig: DEFAULT_CONFIG,
118
+ safetySettings: SAFETY_SETTINGS,
119
+ });
120
+
121
+ try {
122
+ const result = await model.generateContent({
123
+ contents: [
124
+ { role: "model", parts: [{ text: system }] },
125
+ { role: "user", parts: [{ text: content }] },
126
+ { role: "user", parts: [{ text: instruction }] },
127
+ ],
128
+ // systemInstruction: "Respond with valid JSON only",
129
+ generationConfig: {
130
+ responseMimeType: "application/json",
131
+ responseSchema: schema,
132
+ },
133
+ });
134
+
135
+ this.updateUsage(result.response);
136
+
137
+ return result.response.text();
138
+ } catch (error) {
139
+ console.error("Gemini prompt error:", error);
140
+ return JSON.stringify({ error: "AI processing failed" });
141
+ }
142
+ }
143
+
144
+ async promptThinking(
145
+ system: string,
146
+ content: string,
147
+ instruction: string,
148
+ schema?: Schema
149
+ ): Promise<AIPromptResponse> {
150
+ const result = await this.prompt(system, content, instruction, schema);
151
+ const reasoning = await this.generateReasoning(system, `You added: ${result} to ${content}`);
152
+
153
+ console.log(result, reasoning);
154
+ return Array.isArray(result) ? [...result, reasoning] : [result, reasoning];
155
+ }
156
+
157
+ private async generateReasoning(system: string, content: string): Promise<string> {
158
+ const model = this.genAI.getGenerativeModel({
159
+ model: this.models.utility,
160
+ generationConfig: { ...DEFAULT_CONFIG, ...this.config, maxOutputTokens: 60 },
161
+ safetySettings: SAFETY_SETTINGS,
162
+ });
163
+
164
+ const result = await model.generateContent([
165
+ `ROLE: ${system}\n\n ${content}`,
166
+ "In natural language and in character, generate a very short statement that contextualizes the change made.",
167
+ ]);
168
+
169
+ this.updateUsage(result.response);
170
+
171
+ return result.response.text();
172
+ }
173
+
174
+ setPrimaryModel(model: string): void {
175
+ this.models.primary = model;
176
+ }
177
+ setUtilityModel(model: string): void {
178
+ this.models.utility = model;
179
+ }
180
+ setGenerationConfig(config: object): void {
181
+ this.config = config;
182
+ }
183
+
184
+ // API Usage Tracking
185
+ setPricing(input: number, output: number): void {
186
+ this.modelPricing = {
187
+ inputCostPerMToken: input,
188
+ outputCostPerMToken: output,
189
+ };
190
+ }
191
+
192
+ private updateUsage(response: GenerateContentResponse): void {
193
+ const usage = response.usageMetadata || {
194
+ promptTokenCount: 0,
195
+ candidatesTokenCount: 0,
196
+ };
197
+
198
+ this.usageStats.totalCalls++;
199
+ this.usageStats.totalInputTokens += usage.promptTokenCount;
200
+ this.usageStats.totalOutputTokens += usage.candidatesTokenCount;
201
+
202
+ this.usageStats.estimatedCost += this.calculateCost(
203
+ usage.promptTokenCount,
204
+ usage.candidatesTokenCount
205
+ );
206
+ }
207
+
208
+ calculateCost(inputTokens: number, outputTokens: number): number {
209
+ return (
210
+ (inputTokens / 1_000_000) * this.modelPricing.inputCostPerMToken +
211
+ (outputTokens / 1_000_000) * this.modelPricing.outputCostPerMToken
212
+ );
213
+ }
214
+ }
package/mogi.d.ts ADDED
@@ -0,0 +1,225 @@
1
+ import { Schema } from "@google/generative-ai";
2
+ import { AIService, AIPromptResponse } from "./ai-interface";
3
+ import { AIUsageStats } from "./ai-interface";
4
+
5
+ declare type MogiAgentID = string;
6
+ declare type MogiProcessID = string;
7
+ declare type MogiNodeID = string;
8
+
9
+ export interface MogiAgentState {
10
+ id: MogiAgentID;
11
+ attributes: Record<string, any>;
12
+ history: MogiAgentHistory[];
13
+ }
14
+
15
+ export interface MogiAgentHistory {
16
+ timestamp: Date;
17
+ processId?: MogiProcessID;
18
+ nodeId?: MogiNodeID;
19
+ changes: Record<string, any>;
20
+ reasoning?: string;
21
+ }
22
+
23
+ export interface MogiSimulationConfig {
24
+ id: string;
25
+ description: string;
26
+ delayMs: number;
27
+ maxConcurrency: number;
28
+ printLogs: boolean;
29
+ }
30
+
31
+ export interface MogiSimulationState {
32
+ currentProcessId: MogiProcessID | null;
33
+ activeAgents: MogiAgentID[];
34
+ nodeIndex: number;
35
+ isComplete: boolean;
36
+ }
37
+
38
+ export interface MogiProcessConfig {
39
+ retries: number;
40
+ timeoutMs: number;
41
+ }
42
+
43
+ export interface MogiNodeConfig {
44
+ aiService: AIService;
45
+ instructions: string;
46
+ appendMessage?: string;
47
+ schema?: Schema;
48
+ useChainOfThought: boolean;
49
+ }
50
+
51
+ /**
52
+ * Class representing the Mogi simulation.
53
+ */
54
+ export class MogiSimulation {
55
+ constructor(config?: Partial<MogiSimulationConfig>);
56
+ /**
57
+ * Adds an agent to the simulation.
58
+ * @param initialState - The initial state of the agent.
59
+ * @returns The ID of the added agent.
60
+ */
61
+ addAgent(initialState: Omit<MogiAgentState, "id" | "history">): MogiAgentID;
62
+ /**
63
+ * Gets the state of an agent by ID.
64
+ * @param id - The ID of the agent.
65
+ * @returns The state of the agent.
66
+ */
67
+ getAgentState(id: MogiAgentID): MogiAgentState;
68
+ /**
69
+ * Gets the state of all agents in the simulation.
70
+ * @returns An array of agent states.
71
+ */
72
+ getAllAgents(): MogiAgentState[];
73
+ /**
74
+ * Gets the current state of the simulation.
75
+ * @returns The state of the simulation.
76
+ */
77
+ getSimulationState(): MogiSimulationState;
78
+ /**
79
+ * Initializes a process in the simulation.
80
+ * @param process - The process to initialize.
81
+ */
82
+ initializeProcess(process: MogiProcess): Promise<void>;
83
+ /**
84
+ * Executes a single step of the simulation.
85
+ * @returns A promise that resolves to a boolean indicating if there are more steps to execute.
86
+ */
87
+ executeStep(): Promise<boolean>;
88
+ /**
89
+ * Runs a process in the simulation.
90
+ * @param process - The process to run.
91
+ */
92
+ runProcess(process: MogiProcess): Promise<void>;
93
+ /**
94
+ * Tracks an AI service for usage statistics.
95
+ * @param service - The AI service to track.
96
+ */
97
+ private trackAIService(service: AIService): void;
98
+ /**
99
+ * Gets the usage statistics for all tracked AI services.
100
+ * @returns The usage statistics.
101
+ */
102
+ getAIUsage(): AIUsageStats;
103
+ /**
104
+ * Resets the usage statistics for all tracked AI services.
105
+ */
106
+ resetAIUsage(): void;
107
+ }
108
+
109
+ /**
110
+ * Class representing a process in the Mogi simulation.
111
+ */
112
+ export class MogiProcess {
113
+ constructor(id: MogiProcessID, config?: MogiProcessConfig);
114
+ /**
115
+ * Adds a node to the process.
116
+ * @param node - The node to add.
117
+ * @returns The process instance.
118
+ */
119
+ addNode(node: MogiNode): this;
120
+ /**
121
+ * Executes the process for a set of agents.
122
+ * @param agents - The agents to process.
123
+ * @param simConfig - The simulation configuration.
124
+ */
125
+ execute(agents: MogiAgentState[], simConfig: MogiSimulationConfig): Promise<void>;
126
+ /**
127
+ * Resets the execution state of the process.
128
+ */
129
+ resetExecution(): void;
130
+ /**
131
+ * Executes a single step for an agent in the process.
132
+ * @param agent - The agent to process.
133
+ * @param simConfig - The simulation configuration.
134
+ * @returns A promise that resolves to a boolean indicating if there are more steps to execute.
135
+ */
136
+ executeAgentStep(agent: MogiAgentState, simConfig: MogiSimulationConfig): Promise<boolean>;
137
+ /**
138
+ * Gets the AI services used by the process.
139
+ * @returns An array of AI services.
140
+ */
141
+ getAIServices(): AIService[];
142
+ }
143
+
144
+ /**
145
+ * Class representing a node in the Mogi simulation.
146
+ */
147
+ export class MogiNode {
148
+ constructor(
149
+ id: MogiNodeID,
150
+ config: MogiNodeConfig
151
+ );
152
+ /**
153
+ * Executes the node for an agent.
154
+ * @param agent - The agent to process.
155
+ * @param delayMs - The delay in milliseconds before processing.
156
+ */
157
+ execute(agent: MogiAgentState, delayMs: number): Promise<void>;
158
+ /**
159
+ * Gets the AI service used by the node.
160
+ * @returns The AI service.
161
+ */
162
+ getAIService(): AIService;
163
+ }
164
+
165
+ /**
166
+ * Creates a Mogi node with AI-driven instructions.
167
+ * @param ai - The AI service to use.
168
+ * @param instructions - The instructions for the node.
169
+ * @param schema - The schema for the node.
170
+ * @returns The created Mogi node.
171
+ */
172
+ export function createMogiNode(
173
+ ai: AIService,
174
+ instructions: string,
175
+ schema?: Schema
176
+ ): MogiNode;
177
+
178
+ /**
179
+ * Utility function to encapsulate schemas.
180
+ * @param schemas - The schemas to encapsulate.
181
+ * @returns The encapsulated schema.
182
+ */
183
+ export function encapsulateSchema(schemas: { [k: string]: Schema }): Schema;
184
+
185
+ /**
186
+ * Class representing a conditional process in the Mogi simulation.
187
+ */
188
+ export class MogiConditionalProcess extends MogiProcess {
189
+ constructor(
190
+ id: string,
191
+ condition: (agent: MogiAgentState) => boolean,
192
+ trueBranch: MogiProcess,
193
+ falseBranch: MogiProcess,
194
+ nodes?: MogiNode[],
195
+ config?: MogiProcessConfig
196
+ );
197
+ /**
198
+ * Executes a conditional step for a set of agents.
199
+ * @param agents - The agents to process.
200
+ * @param simConfig - The simulation configuration.
201
+ * @returns A promise that resolves to a boolean indicating if there are more steps to execute.
202
+ */
203
+ executeConditionalStep(agents: MogiAgentState[], simConfig: MogiSimulationConfig): Promise<boolean>;
204
+ /**
205
+ * Resets the execution state of the conditional process.
206
+ */
207
+ resetExecution(): void;
208
+ /**
209
+ * Updates the branches of the conditional process.
210
+ * @param trueBranch - The new true branch process.
211
+ * @param falseBranch - The new false branch process.
212
+ */
213
+ updateBranches(trueBranch?: MogiProcess, falseBranch?: MogiProcess): void;
214
+ /**
215
+ * Gets the active branch for an agent.
216
+ * @param agentId - The ID of the agent.
217
+ * @returns The active branch process.
218
+ */
219
+ getActiveBranch(agentId: MogiAgentID): MogiProcess | null;
220
+ /**
221
+ * Gets the AI services used by the conditional process.
222
+ * @returns An array of AI services.
223
+ */
224
+ getAIServices(): AIService[];
225
+ }
package/mogi.ts ADDED
@@ -0,0 +1,447 @@
1
+ // mogi.ts
2
+ import { AIService, AIUsageStats, type AIPromptResponse } from "./ai-interface";
3
+ import { Schema, SchemaType } from "@google/generative-ai";
4
+
5
+ type MogiAgentID = string;
6
+ type MogiProcessID = string;
7
+ type MogiNodeID = string;
8
+
9
+ export interface MogiAgentState {
10
+ id: MogiAgentID;
11
+ attributes: Record<string, any>;
12
+ history: MogiAgentHistory[];
13
+ }
14
+
15
+ export interface MogiAgentHistory {
16
+ timestamp: Date;
17
+ processId?: MogiProcessID;
18
+ nodeId?: MogiNodeID;
19
+ changes: Record<string, any>;
20
+ reasoning?: string;
21
+ }
22
+
23
+ export interface MogiSimulationConfig {
24
+ id: string;
25
+ description: string;
26
+ delayMs: number;
27
+ maxConcurrency: number;
28
+ printLogs: boolean;
29
+ }
30
+
31
+ export interface MogiSimulationState {
32
+ currentProcessId: MogiProcessID | null;
33
+ activeAgents: MogiAgentID[];
34
+ nodeIndex: number;
35
+ isComplete: boolean;
36
+ }
37
+
38
+ export interface MogiProcessConfig {
39
+ retries: number;
40
+ timeoutMs: number;
41
+ }
42
+
43
+ export interface MogiNodeConfig {
44
+ aiService: AIService;
45
+ instructions: string;
46
+ appendMessage?: string;
47
+ schema?: Schema;
48
+ useChainOfThought: boolean;
49
+ }
50
+
51
+ export class MogiSimulation {
52
+ private aiServices: Set<AIService> = new Set();
53
+ private globalUsage: AIUsageStats = {
54
+ totalCalls: 0,
55
+ totalInputTokens: 0,
56
+ totalOutputTokens: 0,
57
+ estimatedCost: 0,
58
+ };
59
+
60
+ private executionQueue: Array<{
61
+ processId: MogiProcessID;
62
+ agentId: MogiAgentID;
63
+ nodeIndex: number;
64
+ }> = [];
65
+ private currentState: MogiSimulationState = {
66
+ currentProcessId: null,
67
+ activeAgents: [],
68
+ nodeIndex: 0,
69
+ isComplete: true,
70
+ };
71
+
72
+ private agents: Map<MogiAgentID, MogiAgentState> = new Map();
73
+ private processes: MogiProcess[] = [];
74
+ private config: MogiSimulationConfig;
75
+
76
+ constructor(config: Partial<MogiSimulationConfig> = {}) {
77
+ this.config = {
78
+ id: crypto.randomUUID(),
79
+ description: "Unnamed Simulation",
80
+ delayMs: 1500,
81
+ maxConcurrency: 5,
82
+ printLogs: false,
83
+ ...config,
84
+ };
85
+ }
86
+
87
+ addAgent(
88
+ initialState: Omit<MogiAgentState, "id" | "history">,
89
+ id: string = crypto.randomUUID()
90
+ ): MogiAgentID {
91
+ this.agents.set(id, {
92
+ id,
93
+ attributes: initialState.attributes,
94
+ history: [],
95
+ });
96
+ return id;
97
+ }
98
+
99
+ public getAgentState(id: MogiAgentID): MogiAgentState {
100
+ const agent = this.agents.get(id);
101
+ if (!agent) {
102
+ throw new Error(`Agent ${id} not found in simulation`);
103
+ }
104
+ return agent;
105
+ }
106
+
107
+ public getAllAgents(): MogiAgentState[] {
108
+ return Array.from(this.agents.values());
109
+ }
110
+
111
+ public getSimulationState(): MogiSimulationState {
112
+ return this.currentState;
113
+ }
114
+
115
+ public async initializeProcess(process: MogiProcess): Promise<void> {
116
+ this.currentState = {
117
+ currentProcessId: process.id,
118
+ activeAgents: Array.from(this.agents.keys()),
119
+ nodeIndex: 0,
120
+ isComplete: false,
121
+ };
122
+ this.executionQueue = [];
123
+
124
+ // Initialize AI Services
125
+ process.getAIServices().forEach((service) => this.trackAIService(service));
126
+
127
+ // Add Process
128
+ this.processes.push(process);
129
+
130
+ // Initialize execution queue
131
+ for (const agentId of this.currentState.activeAgents) {
132
+ this.executionQueue.push({
133
+ processId: process.id,
134
+ agentId,
135
+ nodeIndex: 0,
136
+ });
137
+ }
138
+
139
+ if (this.config.printLogs) {
140
+ console.log(
141
+ `Initialized process ${process.id} with agents: ${this.currentState.activeAgents.join(
142
+ ", "
143
+ )}`
144
+ );
145
+ }
146
+ }
147
+
148
+ public async executeStep(): Promise<boolean> {
149
+ if (this.currentState.isComplete) return false;
150
+
151
+ const currentProcess = this.processes.find((p) => p.id === this.currentState.currentProcessId);
152
+ if (!currentProcess) {
153
+ this.currentState.isComplete = true;
154
+ return false;
155
+ }
156
+
157
+ // Handle conditional processes differently
158
+ if (currentProcess instanceof MogiConditionalProcess) {
159
+ return currentProcess.executeConditionalStep(
160
+ this.currentState.activeAgents.map((id) => this.getAgentState(id)),
161
+ this.config
162
+ );
163
+ }
164
+
165
+ // Original linear execution logic
166
+ const currentNode = currentProcess.nodes[this.currentState.nodeIndex];
167
+ const agents = this.currentState.activeAgents.map((id) => this.getAgentState(id));
168
+
169
+ await Promise.all(agents.map((agent) => currentNode.execute(agent, this.config.delayMs)));
170
+
171
+ this.currentState.nodeIndex++;
172
+ if (this.currentState.nodeIndex >= currentProcess.nodes.length) {
173
+ this.currentState.isComplete = true;
174
+ }
175
+
176
+ return !this.currentState.isComplete;
177
+ }
178
+
179
+ public async runProcess(process: MogiProcess): Promise<void> {
180
+ if (this.config.printLogs) console.log(`Process ${process.id} started.`);
181
+ await this.initializeProcess(process);
182
+ while (await this.executeStep()) {
183
+ if (this.config.printLogs)
184
+ console.log(`Process ${process.id}: Step ${this.currentState.nodeIndex}`);
185
+ await new Promise((resolve) => setTimeout(resolve, this.config.delayMs));
186
+ }
187
+
188
+ if (this.config.printLogs) console.log(`Process ${process.id} completed.`);
189
+ }
190
+
191
+ private trackAIService(service: AIService): void {
192
+ if (!this.aiServices.has(service)) {
193
+ this.aiServices.add(service);
194
+ }
195
+ }
196
+
197
+ public getAIUsage(): AIUsageStats {
198
+ return Array.from(this.aiServices).reduce(
199
+ (acc, service) => {
200
+ const stats = service.getUsageStats();
201
+ acc.totalCalls += stats.totalCalls;
202
+ acc.totalInputTokens += stats.totalInputTokens;
203
+ acc.totalOutputTokens += stats.totalOutputTokens;
204
+ acc.estimatedCost += stats.estimatedCost;
205
+ return acc;
206
+ },
207
+ { ...this.globalUsage }
208
+ );
209
+ }
210
+
211
+ public resetAIUsage(): void {
212
+ this.aiServices.forEach((service) => service.resetUsageStats());
213
+ this.globalUsage = {
214
+ totalCalls: 0,
215
+ totalInputTokens: 0,
216
+ totalOutputTokens: 0,
217
+ estimatedCost: 0,
218
+ };
219
+ }
220
+ }
221
+
222
+ export class MogiProcess {
223
+ public nodes: MogiNode[] = [];
224
+ public currentNodeIndex = 0;
225
+
226
+ constructor(
227
+ public readonly id: MogiProcessID,
228
+ public config: MogiProcessConfig = { retries: 3, timeoutMs: 10000 }
229
+ ) {}
230
+
231
+ addNode(node: MogiNode): this {
232
+ this.nodes.push(node);
233
+ return this;
234
+ }
235
+
236
+ async execute(agents: MogiAgentState[], simConfig: MogiSimulationConfig): Promise<void> {
237
+ for (const agent of agents) {
238
+ await this.runAgentThroughNodes(agent, simConfig);
239
+ }
240
+ }
241
+
242
+ private async runAgentThroughNodes(
243
+ agent: MogiAgentState,
244
+ simConfig: MogiSimulationConfig
245
+ ): Promise<void> {
246
+ for (const node of this.nodes) {
247
+ await node.execute(agent, simConfig.delayMs);
248
+ await new Promise((resolve) => setTimeout(resolve, simConfig.delayMs));
249
+ }
250
+ }
251
+
252
+ public resetExecution(): void {
253
+ this.currentNodeIndex = 0;
254
+ }
255
+
256
+ public async executeAgentStep(
257
+ agent: MogiAgentState,
258
+ simConfig: MogiSimulationConfig
259
+ ): Promise<boolean> {
260
+ if (this.currentNodeIndex < this.nodes.length) {
261
+ await this.nodes[this.currentNodeIndex].execute(agent, simConfig.delayMs);
262
+ this.currentNodeIndex++;
263
+ return true;
264
+ }
265
+ return false;
266
+ }
267
+
268
+ public getAIServices(): AIService[] {
269
+ return this.nodes.map((n) => n.getAIService());
270
+ }
271
+ }
272
+
273
+ export class MogiNode {
274
+ constructor(public readonly id: MogiNodeID, private config: MogiNodeConfig) {}
275
+
276
+ async execute(agent: MogiAgentState, delayMs: number): Promise<void> {
277
+ try {
278
+ const changes = await this.handler(agent);
279
+ this.applyChanges(agent, changes);
280
+ } catch (error) {
281
+ console.error(`MogiNode ${this.id} failed:`, error);
282
+ }
283
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
284
+ }
285
+
286
+ private applyChanges(agent: MogiAgentState, changes: Record<string, any>): void {
287
+ agent.attributes = { ...agent.attributes, ...changes };
288
+ agent.history.push({
289
+ timestamp: new Date(),
290
+ nodeId: this.id,
291
+ changes,
292
+ reasoning: changes._reasoning,
293
+ });
294
+ }
295
+
296
+ private async handler(agent: MogiAgentState) {
297
+ const response = this.config.useChainOfThought
298
+ ? await this.config.aiService.promptThinking(
299
+ this.config.instructions,
300
+ JSON.stringify(agent.attributes),
301
+ this.config.appendMessage ??
302
+ "Process the JSON object and make changes based on your instructions.",
303
+ this.config.schema ?? this.config.aiService.createSchema(agent.attributes)
304
+ )
305
+ : await this.config.aiService.prompt(
306
+ this.config.instructions,
307
+ JSON.stringify(agent.attributes),
308
+ this.config.appendMessage ??
309
+ "Process the JSON object and make changes based on your instructions.",
310
+ this.config.schema ?? this.config.aiService.createSchema(agent.attributes)
311
+ );
312
+ return parseAIReponse(response);
313
+ }
314
+
315
+ public getAIService(): AIService {
316
+ return this.config.aiService;
317
+ }
318
+ }
319
+
320
+ // Utility functions
321
+ export function encapsulateSchema(schemas: { [k: string]: Schema }): Schema {
322
+ return {
323
+ type: SchemaType.OBJECT,
324
+ properties: schemas,
325
+ };
326
+ }
327
+ export function createMogiNode(
328
+ id: string = crypto.randomUUID(),
329
+ ai: AIService,
330
+ instructions: string,
331
+ schemas?: { [k: string]: Schema },
332
+ config?: Omit<MogiNodeConfig, "schema" | "aiService" | "instructions">
333
+ ): MogiNode {
334
+ const newNode = new MogiNode(crypto.randomUUID(), {
335
+ aiService: ai,
336
+ instructions,
337
+ useChainOfThought: false,
338
+ appendMessage: config?.appendMessage,
339
+ schema: schemas ? encapsulateSchema(schemas) : undefined,
340
+ ...config,
341
+ });
342
+
343
+ return newNode;
344
+ }
345
+
346
+ function parseAIReponse(response: AIPromptResponse): Record<string, any> {
347
+ try {
348
+ const [jsonStr, reasoning] = Array.isArray(response) ? response : [response];
349
+ const result = JSON.parse(jsonStr);
350
+ return { ...result, _reasoning: reasoning };
351
+ } catch (error) {
352
+ console.error("Failed to parse AI response:", error);
353
+ return { _error: "Invalid AI response", _raw: response };
354
+ }
355
+ }
356
+
357
+ export class MogiConditionalProcess extends MogiProcess {
358
+ private condition: (agent: MogiAgentState) => boolean;
359
+ private trueBranch: MogiProcess;
360
+ private falseBranch: MogiProcess;
361
+ private branchStates = new Map<
362
+ MogiAgentID,
363
+ {
364
+ branch: MogiProcess;
365
+ completed: boolean;
366
+ }
367
+ >();
368
+
369
+ constructor(
370
+ id: string,
371
+ condition: (agent: MogiAgentState) => boolean,
372
+ trueBranch: MogiProcess,
373
+ falseBranch: MogiProcess,
374
+ nodes: MogiNode[] = [],
375
+ config?: MogiProcessConfig
376
+ ) {
377
+ super(id, config);
378
+ this.condition = condition;
379
+ this.trueBranch = trueBranch;
380
+ this.falseBranch = falseBranch;
381
+ nodes.forEach((n) => this.addNode(n));
382
+ }
383
+
384
+ public async executeConditionalStep(
385
+ agents: MogiAgentState[],
386
+ simConfig: MogiSimulationConfig
387
+ ): Promise<boolean> {
388
+ let hasMoreSteps = false;
389
+
390
+ for (const agent of agents) {
391
+ if (this.currentNodeIndex < this.nodes.length) {
392
+ await this.nodes[this.currentNodeIndex].execute(agent, simConfig.delayMs);
393
+ await new Promise((resolve) => setTimeout(resolve, simConfig.delayMs));
394
+ hasMoreSteps = true;
395
+ continue;
396
+ }
397
+
398
+ // Initialize branch state if not set
399
+ if (!this.branchStates.has(agent.id)) {
400
+ const branch = this.condition(agent) ? this.trueBranch : this.falseBranch;
401
+ branch.resetExecution();
402
+ this.branchStates.set(agent.id, { branch, completed: false });
403
+ }
404
+
405
+ const branchState = this.branchStates.get(agent.id)!;
406
+ if (!branchState.completed) {
407
+ const branchHasSteps = await branchState.branch.executeAgentStep(agent, simConfig);
408
+ if (branchHasSteps) hasMoreSteps = true;
409
+ branchState.completed = !branchHasSteps;
410
+ }
411
+ }
412
+
413
+ // Advance main process index if still in base nodes
414
+ if (this.currentNodeIndex < this.nodes.length) {
415
+ this.currentNodeIndex++;
416
+ return true;
417
+ }
418
+
419
+ return hasMoreSteps;
420
+ }
421
+
422
+ public resetExecution(): void {
423
+ super.resetExecution();
424
+ this.branchStates.clear();
425
+ this.trueBranch.resetExecution();
426
+ this.falseBranch.resetExecution();
427
+ }
428
+
429
+ public updateBranches(trueBranch?: MogiProcess, falseBranch?: MogiProcess): void {
430
+ if (trueBranch) this.trueBranch = trueBranch;
431
+ if (falseBranch) this.falseBranch = falseBranch;
432
+ }
433
+
434
+ public getActiveBranch(agentId: MogiAgentID): MogiProcess | null {
435
+ return this.branchStates.get(agentId)?.branch || null;
436
+ }
437
+
438
+ public getAIServices(): AIService[] {
439
+ return [...this.nodes, this.trueBranch, this.falseBranch].flatMap((n) => {
440
+ if (n instanceof MogiProcess) {
441
+ return n.getAIServices();
442
+ } else {
443
+ return n.getAIService();
444
+ }
445
+ });
446
+ }
447
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@channela9/mogi",
3
+ "version": "0.1.2",
4
+ "description": "A simulation framework that leverages LLMs to simulate various processes and interactions between agents.",
5
+ "main": "mogi.ts",
6
+ "dependencies": {
7
+ "@google/generative-ai": "^0.21.0"
8
+ },
9
+ "devDependencies": {
10
+ "ts-node": "^10.0.0",
11
+ "typescript": "^4.0.0"
12
+ },
13
+ "keywords": [
14
+ "simulation",
15
+ "AI",
16
+ "LLM",
17
+ "agents",
18
+ "process",
19
+ "interaction",
20
+ "framework",
21
+ "nodejs",
22
+ "typescript"
23
+ ],
24
+ "scripts": {
25
+ "test": "echo \"Error: no test specified\" && exit 1"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/channelA9/mogi.git"
30
+ },
31
+ "author": "",
32
+ "license": "MIT",
33
+ "types": "./mogi.d.ts",
34
+ "bugs": {
35
+ "url": "https://github.com/channelA9/mogi/issues"
36
+ },
37
+ "homepage": "https://github.com/channelA9/mogi#readme"
38
+ }