@dxos/assistant 0.8.2 → 0.8.3-main.7f5a14c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +231 -84
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +240 -89
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +231 -84
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/blueprint/ai.test.d.ts +2 -0
- package/dist/types/src/blueprint/ai.test.d.ts.map +1 -0
- package/dist/types/src/blueprint/blueprint.d.ts +23 -48
- package/dist/types/src/blueprint/blueprint.d.ts.map +1 -1
- package/dist/types/src/blueprint/logger.d.ts +24 -2
- package/dist/types/src/blueprint/logger.d.ts.map +1 -1
- package/dist/types/src/blueprint/machine.d.ts +33 -9
- package/dist/types/src/blueprint/machine.d.ts.map +1 -1
- package/dist/types/src/research/graph.d.ts +6 -5
- package/dist/types/src/research/graph.d.ts.map +1 -1
- package/package.json +30 -20
- package/src/blueprint/ai.test.ts +156 -0
- package/src/blueprint/blueprint.test.ts +23 -14
- package/src/blueprint/blueprint.ts +22 -42
- package/src/blueprint/logger.ts +108 -19
- package/src/blueprint/machine.ts +75 -37
- package/src/research/graph.ts +65 -17
- package/src/research/research.test.ts +1 -1
- package/src/research/research.ts +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { AiChat, AiInput, AiLanguageModel, AiTool, AiToolkit } from '@effect/ai';
|
|
6
|
+
import { AnthropicClient, AnthropicLanguageModel } from '@effect/ai-anthropic';
|
|
7
|
+
import { OpenAiClient, OpenAiLanguageModel } from '@effect/ai-openai';
|
|
8
|
+
import { NodeHttpClient } from '@effect/platform-node';
|
|
9
|
+
import { Config, Console, Effect, Layer, pipe, Schedule, Schema } from 'effect';
|
|
10
|
+
import { describe, it } from 'vitest';
|
|
11
|
+
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
|
|
14
|
+
// https://effect.website/docs/ai/tool-use/#5-bring-it-all-together
|
|
15
|
+
// https://github.com/Effect-TS/effect/blob/main/packages/ai/ai/CHANGELOG.md
|
|
16
|
+
// https://discord.com/channels/795981131316985866/1338871274398679130
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Rationale:
|
|
20
|
+
* - Separation of concerns: config, execution pipline, error handling/retry, tool dispatch, async.
|
|
21
|
+
* - Uncouple tool definitions from tool implementation (late binding, composition).
|
|
22
|
+
* - Affinity with effect schema.
|
|
23
|
+
* - Leverage existing effect based conductor pipline.
|
|
24
|
+
* - Simple API for plugins/artifacts.
|
|
25
|
+
* - Ecosystem and design partner.
|
|
26
|
+
*/
|
|
27
|
+
describe.runIf(!process.env.CI)('AiLanguageModel', () => {
|
|
28
|
+
it.runIf(process.env.OPENAI_API_KEY)('Debug: Verify API configuration', async ({ expect }) => {
|
|
29
|
+
const program = Effect.gen(function* () {
|
|
30
|
+
yield* Console.log('Testing API connectivity...');
|
|
31
|
+
const response = yield* AiLanguageModel.generateText({ prompt: 'Hello, respond with "API is working"' });
|
|
32
|
+
yield* Console.log('API Response received:', response.text);
|
|
33
|
+
return response.text;
|
|
34
|
+
}).pipe(
|
|
35
|
+
Effect.provide(OpenAiLanguageModel.model('gpt-4o')),
|
|
36
|
+
Effect.timeout('10 seconds'),
|
|
37
|
+
Effect.retry({ times: 1 }),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const result = await program.pipe(
|
|
41
|
+
Effect.provide([
|
|
42
|
+
OpenAiClient.layerConfig({
|
|
43
|
+
apiKey: Config.redacted('OPENAI_API_KEY'),
|
|
44
|
+
}).pipe(Layer.provide(NodeHttpClient.layerUndici)),
|
|
45
|
+
]),
|
|
46
|
+
Effect.runPromise,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(result).to.contain('working');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Tool definitions.
|
|
53
|
+
class Toolkit extends AiToolkit.make(
|
|
54
|
+
AiTool.make('Calculator', {
|
|
55
|
+
description: 'Test tool',
|
|
56
|
+
parameters: {
|
|
57
|
+
input: Schema.String.annotations({
|
|
58
|
+
description: 'The calculation to perform.',
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
success: Schema.Struct({
|
|
62
|
+
result: Schema.Number,
|
|
63
|
+
}),
|
|
64
|
+
failure: Schema.Never,
|
|
65
|
+
}),
|
|
66
|
+
) {}
|
|
67
|
+
|
|
68
|
+
// Tool handlers.
|
|
69
|
+
const ToolkitLayer = Toolkit.toLayer({
|
|
70
|
+
Calculator: ({ input }) =>
|
|
71
|
+
Effect.gen(function* () {
|
|
72
|
+
const result = (() => {
|
|
73
|
+
// Only allow basic arithmetic operations for safety.
|
|
74
|
+
const sanitizedInput = input.replace(/[^0-9+\-*/().\s]/g, '');
|
|
75
|
+
// eslint-disable-next-line no-new-func
|
|
76
|
+
return Function(`"use strict"; return (${sanitizedInput})`)();
|
|
77
|
+
})();
|
|
78
|
+
|
|
79
|
+
yield* Console.log(`Executing calculation: ${input} = ${result}`);
|
|
80
|
+
return { result };
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Providers.
|
|
85
|
+
const OpenAiLayer = OpenAiClient.layerConfig({
|
|
86
|
+
apiKey: Config.redacted('OPENAI_API_KEY'),
|
|
87
|
+
}).pipe(Layer.provide(NodeHttpClient.layerUndici));
|
|
88
|
+
|
|
89
|
+
const AnthropicLayer = AnthropicClient.layerConfig({
|
|
90
|
+
apiKey: Config.redacted('ANTHROPIC_API_KEY'),
|
|
91
|
+
}).pipe(Layer.provide(NodeHttpClient.layerUndici));
|
|
92
|
+
|
|
93
|
+
it.runIf(process.env.OPENAI_API_KEY)('should make a tool call', async ({ expect }) => {
|
|
94
|
+
const createProgram = (prompt: string) =>
|
|
95
|
+
AiLanguageModel.generateText({
|
|
96
|
+
toolkit: Toolkit,
|
|
97
|
+
prompt,
|
|
98
|
+
}).pipe(
|
|
99
|
+
Effect.tap((response) => Console.log(response)),
|
|
100
|
+
Effect.provide(OpenAiLanguageModel.model('gpt-4o')),
|
|
101
|
+
Effect.retry(pipe(Schedule.exponential('1 second'), Schedule.intersect(Schedule.recurs(2)))),
|
|
102
|
+
Effect.timeout('30 seconds'),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const program = createProgram('What is six times seven? Use appropriate tools and just answer with the number.');
|
|
106
|
+
const result = await program.pipe(Effect.provide([ToolkitLayer, OpenAiLayer]), Effect.runPromise);
|
|
107
|
+
expect(result).toBeDefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// TODO(burdon): Is thisthe effectful way to do this?
|
|
111
|
+
const createChat = (prompt: string) =>
|
|
112
|
+
Effect.gen(function* () {
|
|
113
|
+
const chat = yield* AiChat.empty;
|
|
114
|
+
const toolkit = yield* Toolkit;
|
|
115
|
+
|
|
116
|
+
// Initial request.
|
|
117
|
+
let response = yield* chat.generateText({ toolkit, prompt });
|
|
118
|
+
|
|
119
|
+
// Agentic loop.
|
|
120
|
+
while (response.results.size > 0) {
|
|
121
|
+
response = yield* chat.generateText({ toolkit, prompt: AiInput.empty });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Done.
|
|
125
|
+
return response.text;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it.runIf(process.env.OPENAI_API_KEY)('should process an agentic loop using OpenAI', async ({ expect }) => {
|
|
129
|
+
const chat = createChat('What is six times seven?');
|
|
130
|
+
const result = await chat.pipe(
|
|
131
|
+
Effect.provide(OpenAiLanguageModel.model('gpt-4o')),
|
|
132
|
+
Effect.provide(OpenAiLayer),
|
|
133
|
+
Effect.provide(ToolkitLayer),
|
|
134
|
+
Effect.runPromise,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
log.info('result', { result });
|
|
138
|
+
expect(result).toContain('42');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// TODO(burdon): Fix Anthropic.
|
|
142
|
+
it.runIf(process.env.ANTHROPIC_API_KEY).skip('should process an agentic loop using Anthropic', async ({ expect }) => {
|
|
143
|
+
const chat = createChat('What is six times seven?');
|
|
144
|
+
const result = await chat.pipe(
|
|
145
|
+
Effect.provide(AnthropicLanguageModel.model('claude-3-5-sonnet-latest')),
|
|
146
|
+
Effect.provide(AnthropicLayer),
|
|
147
|
+
Effect.provide(ToolkitLayer),
|
|
148
|
+
Effect.runPromise,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
log.info('result', { result });
|
|
152
|
+
expect(result).toContain('42');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// TODO(burdon): Implement MCP server for ECHO on CF.
|
|
156
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
import { describe, test } from 'vitest';
|
|
7
7
|
|
|
8
|
-
import { AIServiceEdgeClient, ToolResult, createTool } from '@dxos/ai';
|
|
8
|
+
import { AIServiceEdgeClient, ToolRegistry, ToolResult, createTool } from '@dxos/ai';
|
|
9
9
|
import { AI_SERVICE_ENDPOINT, EXA_API_KEY } from '@dxos/ai/testing';
|
|
10
10
|
import { ArtifactId } from '@dxos/artifact';
|
|
11
11
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
@@ -23,7 +23,7 @@ import { createExaTool } from '../research/exa';
|
|
|
23
23
|
|
|
24
24
|
// TODO(burdon): Don't run on CI.
|
|
25
25
|
describe.skip('Blueprint', () => {
|
|
26
|
-
const
|
|
26
|
+
const aiClient = new AIServiceEdgeClient({
|
|
27
27
|
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
28
28
|
defaultGenerationOptions: {
|
|
29
29
|
// model: '@anthropic/claude-sonnet-4-20250514',
|
|
@@ -39,9 +39,10 @@ describe.skip('Blueprint', () => {
|
|
|
39
39
|
.step('Write a pitch deck for the product')
|
|
40
40
|
.build();
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const tools = new ToolRegistry([]);
|
|
43
|
+
const machine = new BlueprintMachine(tools, blueprint);
|
|
43
44
|
setConsolePrinter(machine, true);
|
|
44
|
-
await machine.runToCompletion({
|
|
45
|
+
await machine.runToCompletion({ aiClient });
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
test('email bot', { timeout: 60_000 }, async () => {
|
|
@@ -82,22 +83,23 @@ describe.skip('Blueprint', () => {
|
|
|
82
83
|
'Determine if the email is introduction, question, or spam. Bail if email does not fit into one of these categories.',
|
|
83
84
|
)
|
|
84
85
|
.step('If the email is spam, label it as spam and do not respond.', {
|
|
85
|
-
tools: [labelTool],
|
|
86
|
+
tools: [labelTool.id],
|
|
86
87
|
})
|
|
87
88
|
.step(
|
|
88
89
|
'If the email is an introduction, respond with a short introduction of yourself and ask for more information.',
|
|
89
90
|
{
|
|
90
|
-
tools: [replyTool],
|
|
91
|
+
tools: [replyTool.id],
|
|
91
92
|
},
|
|
92
93
|
)
|
|
93
94
|
.step('If the email is a question, respond with a short answer and ask for more information.', {
|
|
94
|
-
tools: [replyTool],
|
|
95
|
+
tools: [replyTool.id],
|
|
95
96
|
})
|
|
96
97
|
.build();
|
|
97
98
|
|
|
98
|
-
const
|
|
99
|
+
const tools = new ToolRegistry([replyTool, labelTool]);
|
|
100
|
+
const machine = new BlueprintMachine(tools, blueprint);
|
|
99
101
|
setConsolePrinter(machine);
|
|
100
|
-
await machine.runToCompletion({
|
|
102
|
+
await machine.runToCompletion({ aiClient, input: TEST_EMAILS[0] });
|
|
101
103
|
});
|
|
102
104
|
|
|
103
105
|
test.only('research', { timeout: 120_000 }, async () => {
|
|
@@ -123,20 +125,27 @@ describe.skip('Blueprint', () => {
|
|
|
123
125
|
|
|
124
126
|
await db.flush({ indexes: true });
|
|
125
127
|
|
|
128
|
+
const [exa, localSearch, graphWriter] = [
|
|
129
|
+
createExaTool({ apiKey: EXA_API_KEY }),
|
|
130
|
+
createLocalSearchTool(db),
|
|
131
|
+
createGraphWriterTool({ db, schema: DataTypes }),
|
|
132
|
+
];
|
|
133
|
+
|
|
126
134
|
const blueprint = BlueprintBuilder.create()
|
|
127
135
|
.step('Research founders of the organization. Do deep research.', {
|
|
128
|
-
tools: [
|
|
136
|
+
tools: [exa.id],
|
|
129
137
|
})
|
|
130
138
|
.step('Based on your research select matching entires that are already in the graph', {
|
|
131
|
-
tools: [
|
|
139
|
+
tools: [localSearch.id],
|
|
132
140
|
})
|
|
133
141
|
.step('Add researched data to the graph', {
|
|
134
|
-
tools: [
|
|
142
|
+
tools: [localSearch.id, graphWriter.id],
|
|
135
143
|
})
|
|
136
144
|
.build();
|
|
137
145
|
|
|
138
|
-
const
|
|
146
|
+
const tools = new ToolRegistry([exa, localSearch, graphWriter]);
|
|
147
|
+
const machine = new BlueprintMachine(tools, blueprint);
|
|
139
148
|
setConsolePrinter(machine, true);
|
|
140
|
-
await machine.runToCompletion({
|
|
149
|
+
await machine.runToCompletion({ aiClient, input: org1 });
|
|
141
150
|
});
|
|
142
151
|
});
|
|
@@ -4,21 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
import { Schema } from 'effect';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { raise } from '@dxos/debug';
|
|
9
|
-
import { ObjectId } from '@dxos/keys';
|
|
7
|
+
import { Key, Obj, Type } from '@dxos/echo';
|
|
10
8
|
|
|
11
9
|
export const BlueprintStep = Schema.Struct({
|
|
12
|
-
id:
|
|
10
|
+
id: Key.ObjectId,
|
|
13
11
|
instructions: Schema.String,
|
|
14
|
-
|
|
15
|
-
tools: Schema.Array(Tool).pipe(Schema.mutable),
|
|
12
|
+
tools: Schema.optional(Schema.Array(Schema.String)),
|
|
16
13
|
});
|
|
17
14
|
export interface BlueprintStep extends Schema.Schema.Type<typeof BlueprintStep> {}
|
|
18
15
|
|
|
19
|
-
export const
|
|
20
|
-
steps: Schema.Array(BlueprintStep
|
|
16
|
+
export const BlueprintDefinition = Schema.Struct({
|
|
17
|
+
steps: Schema.Array(BlueprintStep.pipe(Schema.omit('id'))),
|
|
21
18
|
});
|
|
19
|
+
export interface BlueprintDefinition extends Schema.Schema.Type<typeof BlueprintDefinition> {}
|
|
20
|
+
|
|
21
|
+
export const Blueprint = Schema.Struct({
|
|
22
|
+
name: Schema.optional(Schema.String),
|
|
23
|
+
steps: Schema.Array(BlueprintStep),
|
|
24
|
+
}).pipe(
|
|
25
|
+
Type.Obj({
|
|
26
|
+
typename: 'dxos.org/type/Blueprint',
|
|
27
|
+
version: '0.1.0',
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
22
30
|
export interface Blueprint extends Schema.Schema.Type<typeof Blueprint> {}
|
|
23
31
|
|
|
24
32
|
/**
|
|
@@ -30,9 +38,9 @@ export namespace BlueprintBuilder {
|
|
|
30
38
|
class Builder {
|
|
31
39
|
private readonly _steps: BlueprintStep[] = [];
|
|
32
40
|
|
|
33
|
-
step(instructions: string, options?: { tools?:
|
|
41
|
+
step(instructions: string, options?: { tools?: string[] }): Builder {
|
|
34
42
|
this._steps.push({
|
|
35
|
-
id: ObjectId.random(),
|
|
43
|
+
id: Key.ObjectId.random(),
|
|
36
44
|
instructions,
|
|
37
45
|
tools: options?.tools ?? [],
|
|
38
46
|
});
|
|
@@ -41,9 +49,7 @@ export namespace BlueprintBuilder {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
build(): Blueprint {
|
|
44
|
-
return {
|
|
45
|
-
steps: this._steps,
|
|
46
|
-
};
|
|
52
|
+
return Obj.make(Blueprint, { steps: this._steps });
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
}
|
|
@@ -52,40 +58,14 @@ export namespace BlueprintBuilder {
|
|
|
52
58
|
* Blueprint parser API.
|
|
53
59
|
*/
|
|
54
60
|
export namespace BlueprintParser {
|
|
55
|
-
export
|
|
56
|
-
instructions: string;
|
|
57
|
-
// TODO(burdon): Tool DXN? Additional metadata?
|
|
58
|
-
tools?: string[];
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export type DSL = {
|
|
62
|
-
steps: Step[];
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const create = (tools: ExecutableTool[] = []) => new Parser(tools);
|
|
61
|
+
export const create = () => new Parser();
|
|
66
62
|
|
|
67
63
|
class Parser {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
toJSON() {
|
|
71
|
-
return {
|
|
72
|
-
tools: this._tools.map((tool) => ({
|
|
73
|
-
name: tool.name,
|
|
74
|
-
namespace: tool.namespace,
|
|
75
|
-
type: tool.type,
|
|
76
|
-
})),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
parse({ steps }: DSL): Blueprint {
|
|
64
|
+
parse({ steps }: BlueprintDefinition): Blueprint {
|
|
81
65
|
const builder = BlueprintBuilder.create();
|
|
82
|
-
|
|
83
66
|
for (const step of steps) {
|
|
84
67
|
builder.step(step.instructions, {
|
|
85
|
-
|
|
86
|
-
tools: step.tools?.map(
|
|
87
|
-
(tool) => this._tools.find(({ id }) => id === tool) ?? raise(new Error(`Tool not found: ${tool}`)),
|
|
88
|
-
),
|
|
68
|
+
tools: (step.tools ?? []) as string[],
|
|
89
69
|
});
|
|
90
70
|
}
|
|
91
71
|
|
package/src/blueprint/logger.ts
CHANGED
|
@@ -2,41 +2,130 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type ReadonlySignal, signal } from '@preact/signals-core';
|
|
5
6
|
import chalk from 'chalk';
|
|
6
7
|
|
|
7
8
|
import { ConsolePrinter } from '@dxos/ai';
|
|
9
|
+
import { type CleanupFn, combine } from '@dxos/async';
|
|
10
|
+
import { log } from '@dxos/log';
|
|
8
11
|
|
|
9
12
|
import { type Blueprint } from './blueprint';
|
|
10
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type BlueprintEvent,
|
|
15
|
+
type BlueprintLogger,
|
|
16
|
+
type BlueprintMachine,
|
|
17
|
+
type BlueprintMachineState,
|
|
18
|
+
} from './machine';
|
|
11
19
|
|
|
12
20
|
/* eslint-disable no-console */
|
|
13
21
|
|
|
14
|
-
// Force chalk colors on for tests
|
|
22
|
+
// Force chalk colors on for tests.
|
|
15
23
|
chalk.level = 2;
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
// TODO(burdon): Factor out.
|
|
26
|
+
type ConsoleLogSignature = (message: string, arg?: any) => void;
|
|
27
|
+
|
|
28
|
+
interface Logger {
|
|
29
|
+
log: ConsoleLogSignature;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_LOGGER: Logger = { log: log.info };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reactive bufferedlogger.
|
|
36
|
+
*/
|
|
37
|
+
export class BufferedLogger implements Logger {
|
|
38
|
+
private _messages = signal<string[]>([]);
|
|
39
|
+
|
|
40
|
+
get messages(): ReadonlySignal<string[]> {
|
|
41
|
+
return this._messages;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
clear() {
|
|
45
|
+
this._messages.value = [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
log(message: string) {
|
|
49
|
+
this._messages.value = [...this._messages.value, message];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// TODO(burdon): Reconcile with ConsolePrinter.
|
|
54
|
+
export class BlueprintLoggerAdapter implements BlueprintLogger {
|
|
55
|
+
constructor(private readonly logger: Logger = DEFAULT_LOGGER) {}
|
|
56
|
+
|
|
57
|
+
log(event: BlueprintEvent) {
|
|
58
|
+
switch (event.type) {
|
|
59
|
+
case 'begin':
|
|
60
|
+
this.logger.log('begin');
|
|
61
|
+
break;
|
|
62
|
+
case 'end':
|
|
63
|
+
this.logger.log('end');
|
|
64
|
+
break;
|
|
65
|
+
case 'step-start':
|
|
66
|
+
this.logger.log('step-start', { step: event.step });
|
|
67
|
+
break;
|
|
68
|
+
case 'step-complete':
|
|
69
|
+
this.logger.log('step-complete', { step: event.step });
|
|
70
|
+
break;
|
|
71
|
+
case 'message':
|
|
72
|
+
this.logger.log('message', { message: event.message });
|
|
73
|
+
break;
|
|
74
|
+
case 'block':
|
|
75
|
+
this.logger.log('block', { block: event.block });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const setLogger = (machine: BlueprintMachine, logger: BufferedLogger): CleanupFn => {
|
|
82
|
+
return combine(
|
|
83
|
+
machine.begin.on(() => {
|
|
84
|
+
logger.log('Starting...');
|
|
85
|
+
}),
|
|
86
|
+
machine.stepStart.on((step) => {
|
|
87
|
+
const index = machine.blueprint.steps.indexOf(step);
|
|
88
|
+
logger.log(`Step ${index + 1} of ${machine.blueprint.steps.length}`);
|
|
89
|
+
logger.log(`Instructions: ${step.instructions}`);
|
|
90
|
+
}),
|
|
91
|
+
machine.stepComplete.on((step) => {
|
|
92
|
+
const trace = machine.state.trace.find((t) => t.stepId === step.id);
|
|
93
|
+
logger.log(`Complete: ${trace?.comment}`);
|
|
94
|
+
}),
|
|
95
|
+
machine.end.on(() => {
|
|
96
|
+
logger.log('OK');
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const setConsolePrinter = (machine: BlueprintMachine, verbose = false): CleanupFn => {
|
|
18
102
|
const printer = new ConsolePrinter();
|
|
19
|
-
|
|
103
|
+
return combine(
|
|
104
|
+
machine.begin.on(() => printTrace(machine.blueprint, machine.state)),
|
|
20
105
|
machine.stepStart.on((step) =>
|
|
21
106
|
console.log(
|
|
22
107
|
`\n${chalk.magenta(`${chalk.bold(`STEP ${machine.blueprint.steps.indexOf(step) + 1} of ${machine.blueprint.steps.length}:`)} ${step.instructions}`)}\n`,
|
|
23
108
|
),
|
|
24
|
-
)
|
|
25
|
-
machine.
|
|
26
|
-
machine.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
109
|
+
),
|
|
110
|
+
machine.stepComplete.on(() => printTrace(machine.blueprint, machine.state)),
|
|
111
|
+
machine.end.on(() => console.log('DONE')),
|
|
112
|
+
verbose
|
|
113
|
+
? [
|
|
114
|
+
machine.message.on((msg) => printer.printMessage(msg)),
|
|
115
|
+
machine.block.on((block) => printer.printContentBlock(block)),
|
|
116
|
+
]
|
|
117
|
+
: [],
|
|
118
|
+
);
|
|
30
119
|
};
|
|
31
120
|
|
|
32
|
-
const
|
|
33
|
-
console.log('\n==============================================\n');
|
|
34
|
-
console.log(chalk.bold('\nThe Blueprint:'));
|
|
121
|
+
const BREAK_LINE = `\n${'_'.repeat(80)}\n`;
|
|
35
122
|
|
|
36
|
-
|
|
123
|
+
const printTrace = (blueprint: Blueprint, state: BlueprintMachineState) => {
|
|
124
|
+
console.group(chalk.bold('\nBlueprint'));
|
|
125
|
+
blueprint.steps.forEach((step) => {
|
|
37
126
|
const traceStep = state.trace.find((t) => t.stepId === step.id);
|
|
38
127
|
|
|
39
|
-
let color = chalk.gray; // Not executed
|
|
128
|
+
let color = chalk.gray; // Not executed.
|
|
40
129
|
let bullet = '○';
|
|
41
130
|
if (traceStep) {
|
|
42
131
|
switch (traceStep.status) {
|
|
@@ -56,11 +145,11 @@ const printTrace = (blueprint: Blueprint, state: BlueprintMachineState) => {
|
|
|
56
145
|
}
|
|
57
146
|
|
|
58
147
|
console.log(color(`\n${bullet} ${step.instructions}`));
|
|
59
|
-
|
|
60
148
|
if (traceStep?.comment) {
|
|
61
|
-
console.log(chalk.white(`
|
|
149
|
+
console.log(chalk.white(` ↳ ${traceStep.comment}`));
|
|
62
150
|
}
|
|
63
151
|
});
|
|
64
|
-
|
|
65
|
-
console.
|
|
152
|
+
|
|
153
|
+
console.groupEnd();
|
|
154
|
+
console.log(BREAK_LINE);
|
|
66
155
|
};
|