@dxos/assistant 0.8.2-main.f11618f → 0.8.2-main.fbd8ed0
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/{chunk-DEIGOD4U.mjs → chunk-B4HAWUFW.mjs} +710 -246
- package/dist/lib/browser/chunk-B4HAWUFW.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +4 -443
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +117 -80
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/{chunk-7VH7NMIM.cjs → chunk-NOB7CIIC.cjs} +716 -259
- package/dist/lib/node/chunk-NOB7CIIC.cjs.map +7 -0
- package/dist/lib/node/index.cjs +34 -470
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +104 -69
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/{chunk-7JI4MDGL.mjs → chunk-SAWYU3V5.mjs} +710 -246
- package/dist/lib/node-esm/chunk-SAWYU3V5.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +4 -443
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +117 -80
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/ai-service/parser.d.ts.map +1 -1
- package/dist/types/src/ai-service/stream.d.ts.map +1 -1
- package/dist/types/src/ai-service/testing/test-stream.d.ts.map +1 -1
- package/dist/types/src/ai-service/types.d.ts +133 -133
- package/dist/types/src/ai-service/types.d.ts.map +1 -1
- package/dist/types/src/ai-service/util/sse.d.ts.map +1 -1
- package/dist/types/src/{conversation → experimental/conversation}/conversation.d.ts +2 -18
- package/dist/types/src/experimental/conversation/conversation.d.ts.map +1 -0
- package/dist/types/src/experimental/conversation/conversation.test.d.ts.map +1 -0
- package/dist/types/src/experimental/conversation/index.d.ts.map +1 -0
- package/dist/types/src/{conversation → experimental/conversation}/types.d.ts +9 -9
- package/dist/types/src/experimental/conversation/types.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/ast.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/combinators.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/combinators.test.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/echo-data-source.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/execution-plan.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/execution-plan.test.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/index.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/parjs.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/parser.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/query-executor.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/query-executor.test.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/schema.d.ts.map +1 -0
- package/dist/types/src/experimental/cypher/schema.test.d.ts.map +1 -0
- package/dist/types/src/experimental/index.d.ts +3 -0
- package/dist/types/src/experimental/index.d.ts.map +1 -0
- package/dist/types/src/experimental/ner/ner.test.d.ts +2 -0
- package/dist/types/src/experimental/ner/ner.test.d.ts.map +1 -0
- package/dist/types/src/experimental/research/exa.d.ts +6 -0
- package/dist/types/src/experimental/research/exa.d.ts.map +1 -0
- package/dist/types/src/experimental/research/research.test.d.ts +2 -0
- package/dist/types/src/experimental/research/research.test.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/session/index.d.ts +1 -0
- package/dist/types/src/session/index.d.ts.map +1 -1
- package/dist/types/src/session/session.d.ts +4 -3
- package/dist/types/src/session/session.d.ts.map +1 -1
- package/dist/types/src/session/tools.d.ts +18 -0
- package/dist/types/src/session/tools.d.ts.map +1 -0
- package/dist/types/src/session/version-pin.d.ts +5 -0
- package/dist/types/src/session/version-pin.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -0
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/logger.d.ts +1 -1
- package/dist/types/src/testing/logger.d.ts.map +1 -1
- package/dist/types/src/testing/ollama.d.ts.map +1 -1
- package/dist/types/src/testing/printer.d.ts +6 -0
- package/dist/types/src/testing/printer.d.ts.map +1 -0
- package/dist/types/src/testing/query-promts.d.ts +4 -4
- package/dist/types/src/testing/query-promts.d.ts.map +1 -1
- package/dist/types/src/testing/test-data.d.ts +5 -5
- package/dist/types/src/testing/test-data.d.ts.map +1 -1
- package/dist/types/src/testing/test-schema.d.ts +25 -23
- package/dist/types/src/testing/test-schema.d.ts.map +1 -1
- package/dist/types/src/util/trim.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +20 -14
- package/src/ai-service/client.test.ts +5 -5
- package/src/ai-service/ollama-client.ts +8 -8
- package/src/ai-service/types.ts +14 -14
- package/src/bin/chat.ts +1 -1
- package/src/{conversation → experimental/conversation}/conversation.test.ts +5 -5
- package/src/{conversation → experimental/conversation}/conversation.ts +3 -112
- package/src/{conversation → experimental/conversation}/types.ts +10 -10
- package/src/{cypher → experimental/cypher}/echo-data-source.ts +11 -10
- package/src/{cypher → experimental/cypher}/query-executor.test.ts +1 -1
- package/src/{cypher → experimental/cypher}/schema.test.ts +1 -1
- package/src/{cypher → experimental/cypher}/schema.ts +1 -1
- package/src/experimental/index.ts +6 -0
- package/src/experimental/ner/ner.test.ts +163 -0
- package/src/experimental/research/exa.ts +38 -0
- package/src/experimental/research/instructions.tpl +64 -0
- package/src/experimental/research/research.test.ts +263 -0
- package/src/index.ts +3 -2
- package/src/session/index.ts +1 -0
- package/src/session/session.test.ts +28 -57
- package/src/session/session.ts +26 -16
- package/src/session/tools.ts +117 -0
- package/src/session/version-pin.ts +6 -0
- package/src/testing/artifacts.test.ts +22 -22
- package/src/testing/index.ts +1 -0
- package/src/testing/logger.ts +1 -1
- package/src/testing/printer.ts +41 -0
- package/src/testing/query-promts.ts +6 -6
- package/src/testing/query.test.ts +1 -2
- package/src/testing/test-data.ts +58 -51
- package/src/testing/test-schema.ts +27 -28
- package/src/typedefs.d.ts +8 -0
- package/dist/lib/browser/chunk-DEIGOD4U.mjs.map +0 -7
- package/dist/lib/node/chunk-7VH7NMIM.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-7JI4MDGL.mjs.map +0 -7
- package/dist/types/src/conversation/conversation.d.ts.map +0 -1
- package/dist/types/src/conversation/conversation.test.d.ts.map +0 -1
- package/dist/types/src/conversation/index.d.ts.map +0 -1
- package/dist/types/src/conversation/types.d.ts.map +0 -1
- package/dist/types/src/cypher/ast.d.ts.map +0 -1
- package/dist/types/src/cypher/combinators.d.ts.map +0 -1
- package/dist/types/src/cypher/combinators.test.d.ts.map +0 -1
- package/dist/types/src/cypher/echo-data-source.d.ts.map +0 -1
- package/dist/types/src/cypher/execution-plan.d.ts.map +0 -1
- package/dist/types/src/cypher/execution-plan.test.d.ts.map +0 -1
- package/dist/types/src/cypher/index.d.ts.map +0 -1
- package/dist/types/src/cypher/parjs.d.ts.map +0 -1
- package/dist/types/src/cypher/parser.d.ts.map +0 -1
- package/dist/types/src/cypher/query-executor.d.ts.map +0 -1
- package/dist/types/src/cypher/query-executor.test.d.ts.map +0 -1
- package/dist/types/src/cypher/schema.d.ts.map +0 -1
- package/dist/types/src/cypher/schema.test.d.ts.map +0 -1
- /package/dist/types/src/{conversation → experimental/conversation}/conversation.test.d.ts +0 -0
- /package/dist/types/src/{conversation → experimental/conversation}/index.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/ast.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/combinators.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/combinators.test.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/echo-data-source.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/execution-plan.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/execution-plan.test.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/index.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/parjs.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/parser.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/query-executor.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/query-executor.test.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/schema.d.ts +0 -0
- /package/dist/types/src/{cypher → experimental/cypher}/schema.test.d.ts +0 -0
- /package/src/{conversation → experimental/conversation}/index.ts +0 -0
- /package/src/{cypher → experimental/cypher}/ast.ts +0 -0
- /package/src/{cypher → experimental/cypher}/combinators.test.ts +0 -0
- /package/src/{cypher → experimental/cypher}/combinators.ts +0 -0
- /package/src/{cypher → experimental/cypher}/execution-plan.test.ts +0 -0
- /package/src/{cypher → experimental/cypher}/execution-plan.ts +0 -0
- /package/src/{cypher → experimental/cypher}/index.ts +0 -0
- /package/src/{cypher → experimental/cypher}/parjs-shims.d.ts +0 -0
- /package/src/{cypher → experimental/cypher}/parjs.ts +0 -0
- /package/src/{cypher → experimental/cypher}/parser.ts +0 -0
- /package/src/{cypher → experimental/cypher}/query-executor.ts +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Option, Schema, SchemaAST } from 'effect';
|
|
6
|
+
import { describe, test } from 'vitest';
|
|
7
|
+
|
|
8
|
+
import { structuredOutputParser } from '@dxos/artifact';
|
|
9
|
+
import { isEncodedReference } from '@dxos/echo-protocol';
|
|
10
|
+
import { create, getSchemaDXN, ObjectId, ReferenceAnnotationId } from '@dxos/echo-schema';
|
|
11
|
+
import { mapAst } from '@dxos/effect';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
13
|
+
import { DataType } from '@dxos/schema';
|
|
14
|
+
import { deepMapValues } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { createExaTool } from './exa';
|
|
17
|
+
import INSTRUCTIONS from './instructions.tpl?raw';
|
|
18
|
+
import { AIServiceEdgeClient, OllamaClient } from '../../ai-service';
|
|
19
|
+
import { AISession } from '../../session';
|
|
20
|
+
import { AI_SERVICE_ENDPOINT, ConsolePrinter } from '../../testing';
|
|
21
|
+
|
|
22
|
+
const EXA_API_KEY = '9c7e17ff-0c85-4cd5-827a-8b489f139e03';
|
|
23
|
+
const REMOTE_AI = true;
|
|
24
|
+
|
|
25
|
+
const aiService = REMOTE_AI
|
|
26
|
+
? new AIServiceEdgeClient({
|
|
27
|
+
endpoint: AI_SERVICE_ENDPOINT.REMOTE,
|
|
28
|
+
})
|
|
29
|
+
: new OllamaClient({
|
|
30
|
+
overrides: {
|
|
31
|
+
model: 'llama3.1:8b',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const TYPES = [DataType.Event, DataType.Organization, DataType.Person, DataType.Project, DataType.Task, DataType.Text];
|
|
36
|
+
|
|
37
|
+
describe.skip('Research', () => {
|
|
38
|
+
test('should generate a research report', { timeout: 1000000 }, async () => {
|
|
39
|
+
const searchTool = createExaTool({ apiKey: EXA_API_KEY });
|
|
40
|
+
|
|
41
|
+
const session = new AISession({ operationModel: 'configured' });
|
|
42
|
+
|
|
43
|
+
const printer = new ConsolePrinter();
|
|
44
|
+
session.message.on((message) => printer.printMessage(message));
|
|
45
|
+
session.userMessage.on((message) => printer.printMessage(message));
|
|
46
|
+
session.block.on((block) => printer.printContentBlock(block));
|
|
47
|
+
|
|
48
|
+
// TODO(dmaretskyi): Consider adding this pattern as the "Graph" output mode for the session.
|
|
49
|
+
const outputSchema = createExtractionSchema(TYPES);
|
|
50
|
+
const result = await session.runStructured(outputSchema, {
|
|
51
|
+
client: aiService,
|
|
52
|
+
systemPrompt: INSTRUCTIONS,
|
|
53
|
+
artifacts: [],
|
|
54
|
+
tools: [searchTool],
|
|
55
|
+
generationOptions: {
|
|
56
|
+
model: '@anthropic/claude-3-5-sonnet-20241022',
|
|
57
|
+
},
|
|
58
|
+
history: [],
|
|
59
|
+
prompt: 'Find projects that are in the space of AI and personal knowledge management',
|
|
60
|
+
});
|
|
61
|
+
const data = sanitizeObjects(TYPES, result as any);
|
|
62
|
+
|
|
63
|
+
log.info('result', { data });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe.skip('misc', () => {
|
|
68
|
+
test('createExtractionSchema', () => {
|
|
69
|
+
const schema = createExtractionSchema(TYPES);
|
|
70
|
+
log.info('schema', { schema });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('extract schema json schema', () => {
|
|
74
|
+
const schema = createExtractionSchema(TYPES);
|
|
75
|
+
const parser = structuredOutputParser(schema);
|
|
76
|
+
log.info('schema', { json: parser.tool.parameters });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('getSanitizedSchemaName', () => {
|
|
80
|
+
const names = TYPES.map(getSanitizedSchemaName);
|
|
81
|
+
log.info('names', { names });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('getTypeAnnotation', () => {
|
|
85
|
+
for (const schema of TYPES) {
|
|
86
|
+
const dxn = getSchemaDXN(schema);
|
|
87
|
+
log.info('dxn', { schema, dxn });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('sanitizeObjects', () => {
|
|
92
|
+
const TEST_DATA = {
|
|
93
|
+
objects_dxos_org_type_Project: [
|
|
94
|
+
{
|
|
95
|
+
name: 'Reor',
|
|
96
|
+
description:
|
|
97
|
+
'A private & local AI personal knowledge management app focused on high entropy thinkers. Features include Q&A chat with full context of notes, automatic connection of ideas, semantic search, and local-first architecture with LLMs and vector databases.',
|
|
98
|
+
image: 'https://reorhomepage-2-cwy0zagzg-reor-team.vercel.app/opengraph-image.jpg',
|
|
99
|
+
id: 'reor-project-01',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'Personal AI',
|
|
103
|
+
description:
|
|
104
|
+
'A platform for creating personal AI models with unlimited memory capabilities. Features include memory stacking, data uploads, and personalized language models that can be trained on individual knowledge bases.',
|
|
105
|
+
image: 'https://cdn.prod.website-files.com/5ff65c460ce39f5ec5681c6a/663d12aab1b425e1ad40d3a6_Memory-min.jpg',
|
|
106
|
+
id: 'personal-ai-project-01',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'IKI AI',
|
|
110
|
+
description:
|
|
111
|
+
'An AI-native workspace for research, strategy, and creative work. Offers features like AI summarization, content analysis, and team knowledge sharing capabilities.',
|
|
112
|
+
image: 'https://framerusercontent.com/assets/cI6Uo7x4q0W3uxzOt2preXjv6aE.jpg',
|
|
113
|
+
id: 'iki-ai-project-01',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'Forethink',
|
|
117
|
+
description:
|
|
118
|
+
'A privacy-first, on-device AI system that proactively links current activities to relevant knowledge. Features real-time learning across teams and local data processing.',
|
|
119
|
+
image: 'https://framerusercontent.com/assets/XDhN6tICnlElB134JWcWXOuXc.jpeg',
|
|
120
|
+
id: 'forethink-project-01',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Amurex',
|
|
124
|
+
description:
|
|
125
|
+
'An invisible AI companion that handles tasks, remembers important information, and supports workflow. Includes features for meeting automation, email categorization, and cross-platform search.',
|
|
126
|
+
image: 'https://amurex.ai/ogimages/og_amurex.jpg',
|
|
127
|
+
id: 'amurex-project-01',
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
objects_dxos_org_type_Organization: [
|
|
131
|
+
{
|
|
132
|
+
name: 'Reor',
|
|
133
|
+
description:
|
|
134
|
+
'A technology company focused on developing local-first AI solutions for personal knowledge management.',
|
|
135
|
+
status: 'active',
|
|
136
|
+
website: 'https://www.reorproject.org',
|
|
137
|
+
id: 'reor-org-01',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'Personal.ai',
|
|
141
|
+
description: 'A company specializing in personal AI development and memory management solutions.',
|
|
142
|
+
status: 'active',
|
|
143
|
+
website: 'https://www.personal.ai',
|
|
144
|
+
id: 'personal-ai-org-01',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'IKI AI',
|
|
148
|
+
description: 'A startup backed by 500 Global, developing AI-native workspaces for knowledge workers.',
|
|
149
|
+
status: 'active',
|
|
150
|
+
website: 'https://iki.ai',
|
|
151
|
+
id: 'iki-ai-org-01',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'Forethink.ai',
|
|
155
|
+
description:
|
|
156
|
+
'A company focused on privacy-first AI solutions for knowledge management and team collaboration.',
|
|
157
|
+
status: 'active',
|
|
158
|
+
website: 'https://www.forethink.ai',
|
|
159
|
+
id: 'forethink-org-01',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
objects_dxos_org_type_Text: [
|
|
163
|
+
{
|
|
164
|
+
content:
|
|
165
|
+
'Current trends in AI-powered Personal Knowledge Management (PKM) systems include: 1) Shift from document-based to graph-based systems for better knowledge connection, 2) Integration of large language models for enhanced content understanding and retrieval, 3) Focus on privacy-first and local-first architectures, 4) Emphasis on automatic knowledge connection and discovery, 5) Implementation of semantic search capabilities.',
|
|
166
|
+
id: 'pkm-trends-text-01',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
content:
|
|
170
|
+
'Key features emerging across modern PKM tools include: automated content categorization, semantic analysis and linking, personalized AI models that learn from user interaction, integration with existing workflows, and robust privacy protection. These tools are increasingly focusing on proactive knowledge surfacing rather than passive storage.',
|
|
171
|
+
id: 'pkm-features-text-01',
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const data = sanitizeObjects(TYPES, TEST_DATA);
|
|
177
|
+
log.info('data', { data });
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const createExtractionSchema = (types: Schema.Schema.AnyNoContext[]) => {
|
|
182
|
+
return Schema.Struct({
|
|
183
|
+
...Object.fromEntries(
|
|
184
|
+
types.map(preprocessSchema).map((schema, index) => [
|
|
185
|
+
`objects_${getSanitizedSchemaName(types[index])}`,
|
|
186
|
+
Schema.optional(Schema.Array(schema)).annotations({
|
|
187
|
+
description: `The objects to answer the query of type: ${getSchemaDXN(types[index])?.asTypeDXN()!.type}`,
|
|
188
|
+
}),
|
|
189
|
+
]),
|
|
190
|
+
),
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const getSanitizedSchemaName = (schema: Schema.Schema.AnyNoContext) => {
|
|
195
|
+
return getSchemaDXN(schema)!
|
|
196
|
+
.asTypeDXN()!
|
|
197
|
+
.type.replaceAll(/[^a-zA-Z0-9]+/g, '_');
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const sanitizeObjects = (types: Schema.Schema.AnyNoContext[], data: Record<string, readonly unknown[]>) => {
|
|
201
|
+
const entries = types
|
|
202
|
+
.map(
|
|
203
|
+
(type) =>
|
|
204
|
+
data[`objects_${getSanitizedSchemaName(type)}`]?.map((object: any) => ({
|
|
205
|
+
data: object,
|
|
206
|
+
schema: type,
|
|
207
|
+
})) ?? [],
|
|
208
|
+
)
|
|
209
|
+
.flat();
|
|
210
|
+
|
|
211
|
+
const idMap = new Map<string, string>();
|
|
212
|
+
|
|
213
|
+
return entries
|
|
214
|
+
.map((entry) => {
|
|
215
|
+
idMap.set(entry.data.id, ObjectId.random());
|
|
216
|
+
entry.data.id = idMap.get(entry.data.id);
|
|
217
|
+
return entry;
|
|
218
|
+
})
|
|
219
|
+
.map((entry) => {
|
|
220
|
+
const data = deepMapValues(entry.data, (value, recurse) => {
|
|
221
|
+
if (isEncodedReference(value)) {
|
|
222
|
+
const ref = value['/'];
|
|
223
|
+
if (idMap.has(ref)) {
|
|
224
|
+
// TODO(dmaretskyi): Whats the best way to represent a local url.
|
|
225
|
+
return { '/': `dxn:echo:@:${idMap.get(ref)}` };
|
|
226
|
+
} else {
|
|
227
|
+
// Search URIs?
|
|
228
|
+
return { '/': `search:?q=${encodeURIComponent(ref)}` };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return recurse(value);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return create(entry.schema, data);
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const SoftRef = Schema.Struct({
|
|
240
|
+
'/': Schema.String,
|
|
241
|
+
}).annotations({
|
|
242
|
+
description: 'Reference to another object.',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const preprocessSchema = (schema: Schema.Schema.AnyNoContext) => {
|
|
246
|
+
const go = (ast: SchemaAST.AST): SchemaAST.AST => {
|
|
247
|
+
if (SchemaAST.getAnnotation(ast, ReferenceAnnotationId).pipe(Option.isSome)) {
|
|
248
|
+
return SoftRef.ast;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return mapAst(ast, go);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const processed = Schema.make<any, any, never>(mapAst(schema.ast, go));
|
|
255
|
+
return Schema.extend(
|
|
256
|
+
processed.pipe(Schema.omit('id')),
|
|
257
|
+
Schema.Struct({
|
|
258
|
+
id: Schema.String.annotations({
|
|
259
|
+
description: 'The id of this object. Come up with a unique id based on your judgement.',
|
|
260
|
+
}),
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
};
|
package/src/index.ts
CHANGED
package/src/session/index.ts
CHANGED
|
@@ -2,35 +2,33 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Schema
|
|
6
|
-
import { inspect } from 'node:util';
|
|
5
|
+
import { Schema } from 'effect';
|
|
7
6
|
import { describe, test } from 'vitest';
|
|
8
7
|
|
|
9
|
-
import {
|
|
10
|
-
ArtifactId,
|
|
11
|
-
defineArtifact,
|
|
12
|
-
defineTool,
|
|
13
|
-
ToolResult,
|
|
14
|
-
type Message,
|
|
15
|
-
type MessageContentBlock,
|
|
16
|
-
} from '@dxos/artifact';
|
|
8
|
+
import { ArtifactId, defineArtifact, defineTool, ToolResult } from '@dxos/artifact';
|
|
17
9
|
import { AIServiceEdgeClient } from '@dxos/assistant';
|
|
18
|
-
import {
|
|
10
|
+
import { Type } from '@dxos/echo';
|
|
11
|
+
import { create, ObjectId } from '@dxos/echo-schema';
|
|
19
12
|
import { DXN } from '@dxos/keys';
|
|
20
13
|
import { log } from '@dxos/log';
|
|
21
14
|
|
|
22
15
|
import { AISession } from './session';
|
|
23
|
-
import { AI_SERVICE_ENDPOINT } from '../testing';
|
|
16
|
+
import { AI_SERVICE_ENDPOINT, ConsolePrinter } from '../testing';
|
|
24
17
|
|
|
25
18
|
// Define a calendar event artifact schema.
|
|
26
|
-
const CalendarEventSchema =
|
|
27
|
-
title:
|
|
28
|
-
startTime:
|
|
29
|
-
endTime:
|
|
30
|
-
description:
|
|
31
|
-
}).pipe(
|
|
19
|
+
const CalendarEventSchema = Schema.Struct({
|
|
20
|
+
title: Schema.String,
|
|
21
|
+
startTime: Schema.String,
|
|
22
|
+
endTime: Schema.String,
|
|
23
|
+
description: Schema.String,
|
|
24
|
+
}).pipe(
|
|
25
|
+
Type.def({
|
|
26
|
+
typename: 'example.com/type/CalendarEvent',
|
|
27
|
+
version: '0.1.0',
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
32
30
|
|
|
33
|
-
type CalendarEvent =
|
|
31
|
+
type CalendarEvent = Schema.Schema.Type<typeof CalendarEventSchema>;
|
|
34
32
|
|
|
35
33
|
// TODO(burdon): Flaky.
|
|
36
34
|
describe.skip('AISession with Ollama', () => {
|
|
@@ -53,7 +51,7 @@ describe.skip('AISession with Ollama', () => {
|
|
|
53
51
|
defineTool('calendar', {
|
|
54
52
|
name: 'query',
|
|
55
53
|
description: 'Query the calendar for events',
|
|
56
|
-
schema:
|
|
54
|
+
schema: Schema.Struct({}),
|
|
57
55
|
execute: async () => {
|
|
58
56
|
return ToolResult.Success(CALENDAR_EVENTS);
|
|
59
57
|
},
|
|
@@ -65,13 +63,13 @@ describe.skip('AISession with Ollama', () => {
|
|
|
65
63
|
id: 'artifact:dxos.org/plugin/table',
|
|
66
64
|
name: 'Table',
|
|
67
65
|
instructions: 'Use this to create and manage tables. Each table has a unique id.',
|
|
68
|
-
schema:
|
|
66
|
+
schema: Schema.Struct({}),
|
|
69
67
|
tools: [
|
|
70
68
|
defineTool('table', {
|
|
71
69
|
name: 'create',
|
|
72
70
|
description: 'Create a table',
|
|
73
|
-
schema:
|
|
74
|
-
data:
|
|
71
|
+
schema: Schema.Struct({
|
|
72
|
+
data: Schema.Array(Schema.Any).annotations({ description: 'Array of data payloads to add as rows' }),
|
|
75
73
|
}),
|
|
76
74
|
execute: async ({ data }) => {
|
|
77
75
|
log('create table', { data });
|
|
@@ -89,12 +87,12 @@ describe.skip('AISession with Ollama', () => {
|
|
|
89
87
|
name: 'Map',
|
|
90
88
|
instructions:
|
|
91
89
|
'Use this to create and manage maps. Maps source data from tables. Table id is required to create a map.',
|
|
92
|
-
schema:
|
|
90
|
+
schema: Schema.Struct({}),
|
|
93
91
|
tools: [
|
|
94
92
|
defineTool('map', {
|
|
95
93
|
name: 'create',
|
|
96
94
|
description: 'Create a map',
|
|
97
|
-
schema:
|
|
95
|
+
schema: Schema.Struct({
|
|
98
96
|
source: ArtifactId.annotations({
|
|
99
97
|
description: 'The table that will be used as the source of the map',
|
|
100
98
|
}),
|
|
@@ -119,7 +117,7 @@ describe.skip('AISession with Ollama', () => {
|
|
|
119
117
|
id: 'artifact:dxos.org/plugin/script',
|
|
120
118
|
name: 'Script',
|
|
121
119
|
instructions: 'Use this to create and manage scripts',
|
|
122
|
-
schema:
|
|
120
|
+
schema: Schema.Struct({}),
|
|
123
121
|
tools: [],
|
|
124
122
|
});
|
|
125
123
|
|
|
@@ -127,9 +125,10 @@ describe.skip('AISession with Ollama', () => {
|
|
|
127
125
|
// printStreamEvent(event);
|
|
128
126
|
// });
|
|
129
127
|
|
|
130
|
-
|
|
131
|
-
session.
|
|
132
|
-
session.
|
|
128
|
+
const printer = new ConsolePrinter();
|
|
129
|
+
session.message.on((message) => printer.printMessage(message));
|
|
130
|
+
session.userMessage.on((message) => printer.printMessage(message));
|
|
131
|
+
session.block.on((block) => printer.printContentBlock(block));
|
|
133
132
|
|
|
134
133
|
// session.update.on((update) => {
|
|
135
134
|
// log('update', { update });
|
|
@@ -189,34 +188,6 @@ const CALENDAR_EVENTS: CalendarEvent[] = [
|
|
|
189
188
|
}),
|
|
190
189
|
];
|
|
191
190
|
|
|
192
|
-
const printMessage = (message: Message) => {
|
|
193
|
-
console.log(`${message.role.toUpperCase()}\n`);
|
|
194
|
-
for (const content of message.content) {
|
|
195
|
-
printContentBlock(content);
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const printContentBlock = (content: MessageContentBlock) => {
|
|
200
|
-
switch (content.type) {
|
|
201
|
-
case 'text':
|
|
202
|
-
console.log(content.text);
|
|
203
|
-
break;
|
|
204
|
-
case 'tool_use':
|
|
205
|
-
console.log(`⚙️ [Tool Use] ${content.name} ${inspect(content.input, { depth: null, colors: true })}`);
|
|
206
|
-
break;
|
|
207
|
-
case 'tool_result': {
|
|
208
|
-
let data: any;
|
|
209
|
-
try {
|
|
210
|
-
data = JSON.parse(content.content);
|
|
211
|
-
} catch {
|
|
212
|
-
data = content.content;
|
|
213
|
-
}
|
|
214
|
-
console.log(`⚙️ [Tool Result] ${content.toolUseId} ${inspect(data, { depth: null, colors: true })}`);
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
191
|
// const printStreamEvent = (event: GenerationStreamEvent) => {
|
|
221
192
|
// switch (event.type) {
|
|
222
193
|
// case 'message_start': {
|
package/src/session/session.ts
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Option, Schema
|
|
5
|
+
import { Option, Schema } from 'effect';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
defineTool,
|
|
9
9
|
Message,
|
|
10
|
+
structuredOutputParser,
|
|
10
11
|
ToolResult,
|
|
11
12
|
type ArtifactDefinition,
|
|
12
13
|
type MessageContentBlock,
|
|
@@ -18,9 +19,9 @@ import { create, type ObjectId } from '@dxos/echo-schema';
|
|
|
18
19
|
import { invariant } from '@dxos/invariant';
|
|
19
20
|
import { log } from '@dxos/log';
|
|
20
21
|
|
|
22
|
+
import { isToolUse, runTools } from './tools';
|
|
21
23
|
import { VersionPin } from './version-pin';
|
|
22
24
|
import { MixedStreamParser, type AIServiceClient, type GenerateRequest, type GenerationStream } from '../ai-service';
|
|
23
|
-
import { isToolUse, runTools } from '../conversation';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Contains message history, tools, current context.
|
|
@@ -230,7 +231,7 @@ export class AISession {
|
|
|
230
231
|
} catch (err) {
|
|
231
232
|
log.catch(err);
|
|
232
233
|
if (err instanceof Error && err.message.includes('Overloaded')) {
|
|
233
|
-
error = new AIServiecOverloadedError('AI service overloaded', { cause: err });
|
|
234
|
+
error = new AIServiecOverloadedError('AI service overloaded', { cause: err } as any); // TODO(dmaretskyi): Schema errors cant have cause
|
|
234
235
|
} else {
|
|
235
236
|
error = new Error('AI service error', { cause: err });
|
|
236
237
|
}
|
|
@@ -245,6 +246,15 @@ export class AISession {
|
|
|
245
246
|
return this._pending;
|
|
246
247
|
}
|
|
247
248
|
|
|
249
|
+
async runStructured<S extends Schema.Schema.AnyNoContext>(schema: S, options: SessionRunOptions) {
|
|
250
|
+
const parser = structuredOutputParser(schema);
|
|
251
|
+
const result = await this.run({
|
|
252
|
+
...options,
|
|
253
|
+
tools: [...options.tools, parser.tool],
|
|
254
|
+
});
|
|
255
|
+
return parser.getResult(result);
|
|
256
|
+
}
|
|
257
|
+
|
|
248
258
|
private async _formatUserPrompt(
|
|
249
259
|
artifactDiffResolver: ArtifactDiffResolver | undefined,
|
|
250
260
|
prompt: string,
|
|
@@ -287,7 +297,7 @@ export class AISession {
|
|
|
287
297
|
return defineTool('system', {
|
|
288
298
|
name: 'query_artifact_definitions',
|
|
289
299
|
description: 'Query the available artifact definitions',
|
|
290
|
-
schema:
|
|
300
|
+
schema: Schema.Struct({}),
|
|
291
301
|
execute: async () => {
|
|
292
302
|
return ToolResult.Success({
|
|
293
303
|
artifactDefinitions: artifacts.map((artifact) => ({
|
|
@@ -308,8 +318,8 @@ export class AISession {
|
|
|
308
318
|
name: 'require_artifact_definitions',
|
|
309
319
|
description:
|
|
310
320
|
'Require the use of specific artifact definitions. This will allow the model to interact with artifact definitions and use their tools.',
|
|
311
|
-
schema:
|
|
312
|
-
artifactDefinitionIds:
|
|
321
|
+
schema: Schema.Struct({
|
|
322
|
+
artifactDefinitionIds: Schema.Array(Schema.String).annotations({
|
|
313
323
|
description: 'The ids of the artifact definitions to require',
|
|
314
324
|
examples: [['artifact:dxos.org/example/Test']],
|
|
315
325
|
}),
|
|
@@ -336,20 +346,20 @@ export class AISession {
|
|
|
336
346
|
name: 'create_plan',
|
|
337
347
|
description:
|
|
338
348
|
'Create a plan. Make sure that each step is independent and can be executed solely on the data returned by the previous step. The steps only share the data that is specified in the plan.',
|
|
339
|
-
schema:
|
|
340
|
-
goal:
|
|
341
|
-
steps:
|
|
342
|
-
|
|
343
|
-
action:
|
|
349
|
+
schema: Schema.Struct({
|
|
350
|
+
goal: Schema.String.annotations({ description: 'The goal that the plan will achieve.' }),
|
|
351
|
+
steps: Schema.Array(
|
|
352
|
+
Schema.Struct({
|
|
353
|
+
action: Schema.String.annotations({
|
|
344
354
|
description: 'Complete, detailed action to perform.',
|
|
345
355
|
}),
|
|
346
|
-
input:
|
|
356
|
+
input: Schema.String.annotations({
|
|
347
357
|
description: 'The required input data for this step. First step must not require any data.',
|
|
348
358
|
}),
|
|
349
|
-
output:
|
|
359
|
+
output: Schema.String.annotations({
|
|
350
360
|
description: 'The output data from this step. This is passed to the next step as input.',
|
|
351
361
|
}),
|
|
352
|
-
requiredArtifactIds:
|
|
362
|
+
requiredArtifactIds: Schema.Array(Schema.String).annotations({
|
|
353
363
|
description: 'The ids of the artifacts required to perform this step.',
|
|
354
364
|
examples: [['artifact:dxos.org/example/Test']],
|
|
355
365
|
}),
|
|
@@ -473,7 +483,7 @@ const createBaseInstructions = ({
|
|
|
473
483
|
${availableArtifacts.length > 0 ? `Artifacts already in context: ${availableArtifacts.join('\n')}` : ''}
|
|
474
484
|
`;
|
|
475
485
|
|
|
476
|
-
export class AIServiecOverloadedError extends
|
|
486
|
+
export class AIServiecOverloadedError extends Schema.TaggedError<AIServiecOverloadedError>()(
|
|
477
487
|
'AIServiecOverloadedError',
|
|
478
488
|
{},
|
|
479
489
|
) {}
|
|
@@ -483,7 +493,7 @@ const gatherObjectVersions = (messages: Message[]): Map<ObjectId, ObjectVersion>
|
|
|
483
493
|
for (const message of messages) {
|
|
484
494
|
for (const block of message.content) {
|
|
485
495
|
if (block.type === 'json' && block.disposition === VersionPin.DISPOSITION) {
|
|
486
|
-
const pin = VersionPin.pipe(
|
|
496
|
+
const pin = VersionPin.pipe(Schema.decodeOption)(JSON.parse(block.json)).pipe(Option.getOrUndefined);
|
|
487
497
|
if (!pin) {
|
|
488
498
|
continue;
|
|
489
499
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Message, type Tool, ToolResult } from '@dxos/artifact';
|
|
6
|
+
import { create } from '@dxos/echo-schema';
|
|
7
|
+
import { invariant } from '@dxos/invariant';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
|
|
10
|
+
export const isToolUse = (message: Message, { onlyToolNames }: { onlyToolNames?: string[] } = {}) => {
|
|
11
|
+
const block = message.content.at(-1);
|
|
12
|
+
invariant(block);
|
|
13
|
+
return block.type === 'tool_use' && (!onlyToolNames || onlyToolNames.includes(block.name));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type RunToolsOptions = {
|
|
17
|
+
message: Message;
|
|
18
|
+
tools: Tool[];
|
|
19
|
+
extensions?: ToolContextExtensions;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type RunToolsResult =
|
|
23
|
+
| {
|
|
24
|
+
type: 'continue';
|
|
25
|
+
message: Message;
|
|
26
|
+
}
|
|
27
|
+
| {
|
|
28
|
+
type: 'break';
|
|
29
|
+
result: unknown;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const runTools = async ({ message, tools, extensions }: RunToolsOptions): Promise<RunToolsResult> => {
|
|
33
|
+
const toolCalls = message.content.filter((block) => block.type === 'tool_use');
|
|
34
|
+
invariant(toolCalls.length === 1);
|
|
35
|
+
const toolCall = toolCalls[0];
|
|
36
|
+
const tool = tools.find((tool) => tool.name === toolCall.name);
|
|
37
|
+
if (!tool) {
|
|
38
|
+
const resultMessage = create(Message, {
|
|
39
|
+
role: 'user',
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: 'tool_result',
|
|
43
|
+
toolUseId: toolCall.id,
|
|
44
|
+
content: `Tool not found: ${toolCall.name}`,
|
|
45
|
+
isError: true,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// TODO(burdon): Retry count?
|
|
51
|
+
return {
|
|
52
|
+
type: 'continue',
|
|
53
|
+
message: resultMessage,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let toolResult: ToolResult;
|
|
58
|
+
try {
|
|
59
|
+
invariant(tool.execute);
|
|
60
|
+
toolResult = await tool.execute(toolCall.input, { extensions });
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
log('tool error', { error });
|
|
63
|
+
toolResult = ToolResult.Error(error.message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
switch (toolResult.kind) {
|
|
67
|
+
case 'error': {
|
|
68
|
+
log('tool error', { message: toolResult.message });
|
|
69
|
+
const resultMessage = create(Message, {
|
|
70
|
+
role: 'user',
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'tool_result',
|
|
74
|
+
toolUseId: toolCall.id,
|
|
75
|
+
content: toolResult.message,
|
|
76
|
+
isError: true,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// TODO(burdon): Retry count?
|
|
82
|
+
return {
|
|
83
|
+
type: 'continue',
|
|
84
|
+
message: resultMessage,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case 'success': {
|
|
89
|
+
log('tool success', { result: toolResult.result });
|
|
90
|
+
const resultMessage = create(Message, {
|
|
91
|
+
role: 'user',
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: 'tool_result',
|
|
95
|
+
toolUseId: toolCall.id,
|
|
96
|
+
content:
|
|
97
|
+
typeof toolResult.result === 'string' ? toolResult.result : JSON.stringify(toolResult.result) ?? '',
|
|
98
|
+
},
|
|
99
|
+
...(toolResult.extractContentBlocks ?? []),
|
|
100
|
+
],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
type: 'continue',
|
|
105
|
+
message: resultMessage,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case 'break': {
|
|
110
|
+
log('tool break', { result: toolResult.result });
|
|
111
|
+
return {
|
|
112
|
+
type: 'break',
|
|
113
|
+
result: toolResult.result,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
@@ -18,6 +18,12 @@ const VersionPinSchema = Schema.Struct({
|
|
|
18
18
|
// TODO(dmaretskyi): Could be opaque
|
|
19
19
|
version: ObjectVersionSchema,
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Used to pin a specific version of an object during the course of a conversation.
|
|
24
|
+
*
|
|
25
|
+
* This allows us to detect when the object changes during the course of the conversation.
|
|
26
|
+
*/
|
|
21
27
|
export interface VersionPin extends Schema.Schema.Type<typeof VersionPinSchema> {}
|
|
22
28
|
|
|
23
29
|
export const VersionPin: typeof VersionPinSchema & {
|