@dxos/assistant 0.8.2-main.2f9c567 → 0.8.2-main.30e4dbb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/lib/browser/{chunk-OLO6SVOL.mjs → chunk-YSYP7S4W.mjs} +693 -229
  2. package/dist/lib/browser/chunk-YSYP7S4W.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +4 -443
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +42 -1
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/{chunk-CI5Q7FBG.cjs → chunk-FTIDW4BA.cjs} +714 -257
  9. package/dist/lib/node/chunk-FTIDW4BA.cjs.map +7 -0
  10. package/dist/lib/node/index.cjs +34 -470
  11. package/dist/lib/node/index.cjs.map +4 -4
  12. package/dist/lib/node/meta.json +1 -1
  13. package/dist/lib/node/testing/index.cjs +49 -9
  14. package/dist/lib/node/testing/index.cjs.map +4 -4
  15. package/dist/lib/node-esm/{chunk-Y3SYOFFJ.mjs → chunk-GCJNPQDJ.mjs} +693 -229
  16. package/dist/lib/node-esm/chunk-GCJNPQDJ.mjs.map +7 -0
  17. package/dist/lib/node-esm/index.mjs +4 -443
  18. package/dist/lib/node-esm/index.mjs.map +4 -4
  19. package/dist/lib/node-esm/meta.json +1 -1
  20. package/dist/lib/node-esm/testing/index.mjs +42 -1
  21. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  22. package/dist/types/src/ai-service/parser.d.ts.map +1 -1
  23. package/dist/types/src/ai-service/stream.d.ts.map +1 -1
  24. package/dist/types/src/ai-service/testing/test-stream.d.ts.map +1 -1
  25. package/dist/types/src/ai-service/types.d.ts.map +1 -1
  26. package/dist/types/src/ai-service/util/sse.d.ts.map +1 -1
  27. package/dist/types/src/{conversation → experimental/conversation}/conversation.d.ts +2 -18
  28. package/dist/types/src/experimental/conversation/conversation.d.ts.map +1 -0
  29. package/dist/types/src/experimental/conversation/conversation.test.d.ts.map +1 -0
  30. package/dist/types/src/experimental/conversation/index.d.ts.map +1 -0
  31. package/dist/types/src/experimental/conversation/types.d.ts.map +1 -0
  32. package/dist/types/src/experimental/cypher/ast.d.ts.map +1 -0
  33. package/dist/types/src/experimental/cypher/combinators.d.ts.map +1 -0
  34. package/dist/types/src/experimental/cypher/combinators.test.d.ts.map +1 -0
  35. package/dist/types/src/experimental/cypher/echo-data-source.d.ts.map +1 -0
  36. package/dist/types/src/experimental/cypher/execution-plan.d.ts.map +1 -0
  37. package/dist/types/src/experimental/cypher/execution-plan.test.d.ts.map +1 -0
  38. package/dist/types/src/experimental/cypher/index.d.ts.map +1 -0
  39. package/dist/types/src/experimental/cypher/parjs.d.ts.map +1 -0
  40. package/dist/types/src/experimental/cypher/parser.d.ts.map +1 -0
  41. package/dist/types/src/experimental/cypher/query-executor.d.ts.map +1 -0
  42. package/dist/types/src/experimental/cypher/query-executor.test.d.ts.map +1 -0
  43. package/dist/types/src/experimental/cypher/schema.d.ts.map +1 -0
  44. package/dist/types/src/experimental/cypher/schema.test.d.ts.map +1 -0
  45. package/dist/types/src/experimental/index.d.ts +3 -0
  46. package/dist/types/src/experimental/index.d.ts.map +1 -0
  47. package/dist/types/src/experimental/ner/ner.test.d.ts +2 -0
  48. package/dist/types/src/experimental/ner/ner.test.d.ts.map +1 -0
  49. package/dist/types/src/experimental/research/exa.d.ts +6 -0
  50. package/dist/types/src/experimental/research/exa.d.ts.map +1 -0
  51. package/dist/types/src/experimental/research/research.test.d.ts +2 -0
  52. package/dist/types/src/experimental/research/research.test.d.ts.map +1 -0
  53. package/dist/types/src/index.d.ts +1 -2
  54. package/dist/types/src/index.d.ts.map +1 -1
  55. package/dist/types/src/session/index.d.ts +1 -0
  56. package/dist/types/src/session/index.d.ts.map +1 -1
  57. package/dist/types/src/session/session.d.ts +1 -0
  58. package/dist/types/src/session/session.d.ts.map +1 -1
  59. package/dist/types/src/session/tools.d.ts +18 -0
  60. package/dist/types/src/session/tools.d.ts.map +1 -0
  61. package/dist/types/src/session/version-pin.d.ts +5 -0
  62. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  63. package/dist/types/src/testing/index.d.ts +1 -0
  64. package/dist/types/src/testing/index.d.ts.map +1 -1
  65. package/dist/types/src/testing/logger.d.ts +1 -1
  66. package/dist/types/src/testing/logger.d.ts.map +1 -1
  67. package/dist/types/src/testing/ollama.d.ts.map +1 -1
  68. package/dist/types/src/testing/printer.d.ts +6 -0
  69. package/dist/types/src/testing/printer.d.ts.map +1 -0
  70. package/dist/types/src/testing/query-promts.d.ts +1 -1
  71. package/dist/types/src/testing/query-promts.d.ts.map +1 -1
  72. package/dist/types/src/testing/test-data.d.ts +1 -1
  73. package/dist/types/src/testing/test-data.d.ts.map +1 -1
  74. package/dist/types/src/util/trim.d.ts.map +1 -1
  75. package/dist/types/tsconfig.tsbuildinfo +1 -1
  76. package/package.json +19 -14
  77. package/src/ai-service/ollama-client.ts +1 -1
  78. package/src/bin/chat.ts +1 -1
  79. package/src/{conversation → experimental/conversation}/conversation.test.ts +2 -2
  80. package/src/{conversation → experimental/conversation}/conversation.ts +3 -112
  81. package/src/{cypher → experimental/cypher}/query-executor.test.ts +1 -1
  82. package/src/{cypher → experimental/cypher}/schema.test.ts +1 -1
  83. package/src/{cypher → experimental/cypher}/schema.ts +1 -1
  84. package/src/experimental/index.ts +6 -0
  85. package/src/experimental/ner/ner.test.ts +163 -0
  86. package/src/experimental/research/exa.ts +38 -0
  87. package/src/experimental/research/instructions.tpl +64 -0
  88. package/src/experimental/research/research.test.ts +263 -0
  89. package/src/index.ts +3 -2
  90. package/src/session/index.ts +1 -0
  91. package/src/session/session.test.ts +6 -41
  92. package/src/session/session.ts +11 -1
  93. package/src/session/tools.ts +117 -0
  94. package/src/session/version-pin.ts +6 -0
  95. package/src/testing/index.ts +1 -0
  96. package/src/testing/logger.ts +1 -1
  97. package/src/testing/printer.ts +41 -0
  98. package/src/testing/query-promts.ts +1 -1
  99. package/src/testing/query.test.ts +1 -2
  100. package/src/testing/test-data.ts +1 -1
  101. package/src/typedefs.d.ts +8 -0
  102. package/dist/lib/browser/chunk-OLO6SVOL.mjs.map +0 -7
  103. package/dist/lib/node/chunk-CI5Q7FBG.cjs.map +0 -7
  104. package/dist/lib/node-esm/chunk-Y3SYOFFJ.mjs.map +0 -7
  105. package/dist/types/src/conversation/conversation.d.ts.map +0 -1
  106. package/dist/types/src/conversation/conversation.test.d.ts.map +0 -1
  107. package/dist/types/src/conversation/index.d.ts.map +0 -1
  108. package/dist/types/src/conversation/types.d.ts.map +0 -1
  109. package/dist/types/src/cypher/ast.d.ts.map +0 -1
  110. package/dist/types/src/cypher/combinators.d.ts.map +0 -1
  111. package/dist/types/src/cypher/combinators.test.d.ts.map +0 -1
  112. package/dist/types/src/cypher/echo-data-source.d.ts.map +0 -1
  113. package/dist/types/src/cypher/execution-plan.d.ts.map +0 -1
  114. package/dist/types/src/cypher/execution-plan.test.d.ts.map +0 -1
  115. package/dist/types/src/cypher/index.d.ts.map +0 -1
  116. package/dist/types/src/cypher/parjs.d.ts.map +0 -1
  117. package/dist/types/src/cypher/parser.d.ts.map +0 -1
  118. package/dist/types/src/cypher/query-executor.d.ts.map +0 -1
  119. package/dist/types/src/cypher/query-executor.test.d.ts.map +0 -1
  120. package/dist/types/src/cypher/schema.d.ts.map +0 -1
  121. package/dist/types/src/cypher/schema.test.d.ts.map +0 -1
  122. /package/dist/types/src/{conversation → experimental/conversation}/conversation.test.d.ts +0 -0
  123. /package/dist/types/src/{conversation → experimental/conversation}/index.d.ts +0 -0
  124. /package/dist/types/src/{conversation → experimental/conversation}/types.d.ts +0 -0
  125. /package/dist/types/src/{cypher → experimental/cypher}/ast.d.ts +0 -0
  126. /package/dist/types/src/{cypher → experimental/cypher}/combinators.d.ts +0 -0
  127. /package/dist/types/src/{cypher → experimental/cypher}/combinators.test.d.ts +0 -0
  128. /package/dist/types/src/{cypher → experimental/cypher}/echo-data-source.d.ts +0 -0
  129. /package/dist/types/src/{cypher → experimental/cypher}/execution-plan.d.ts +0 -0
  130. /package/dist/types/src/{cypher → experimental/cypher}/execution-plan.test.d.ts +0 -0
  131. /package/dist/types/src/{cypher → experimental/cypher}/index.d.ts +0 -0
  132. /package/dist/types/src/{cypher → experimental/cypher}/parjs.d.ts +0 -0
  133. /package/dist/types/src/{cypher → experimental/cypher}/parser.d.ts +0 -0
  134. /package/dist/types/src/{cypher → experimental/cypher}/query-executor.d.ts +0 -0
  135. /package/dist/types/src/{cypher → experimental/cypher}/query-executor.test.d.ts +0 -0
  136. /package/dist/types/src/{cypher → experimental/cypher}/schema.d.ts +0 -0
  137. /package/dist/types/src/{cypher → experimental/cypher}/schema.test.d.ts +0 -0
  138. /package/src/{conversation → experimental/conversation}/index.ts +0 -0
  139. /package/src/{conversation → experimental/conversation}/types.ts +0 -0
  140. /package/src/{cypher → experimental/cypher}/ast.ts +0 -0
  141. /package/src/{cypher → experimental/cypher}/combinators.test.ts +0 -0
  142. /package/src/{cypher → experimental/cypher}/combinators.ts +0 -0
  143. /package/src/{cypher → experimental/cypher}/echo-data-source.ts +0 -0
  144. /package/src/{cypher → experimental/cypher}/execution-plan.test.ts +0 -0
  145. /package/src/{cypher → experimental/cypher}/execution-plan.ts +0 -0
  146. /package/src/{cypher → experimental/cypher}/index.ts +0 -0
  147. /package/src/{cypher → experimental/cypher}/parjs-shims.d.ts +0 -0
  148. /package/src/{cypher → experimental/cypher}/parjs.ts +0 -0
  149. /package/src/{cypher → experimental/cypher}/parser.ts +0 -0
  150. /package/src/{cypher → experimental/cypher}/query-executor.ts +0 -0
@@ -0,0 +1,38 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+ import Exa from 'exa-js';
7
+
8
+ import { defineTool, ToolResult } from '@dxos/artifact';
9
+ import { log } from '@dxos/log';
10
+
11
+ type CreateExaToolOptions = {
12
+ apiKey: string;
13
+ };
14
+
15
+ export const createExaTool = ({ apiKey }: CreateExaToolOptions) => {
16
+ const exa = new Exa(apiKey);
17
+
18
+ return defineTool('search', {
19
+ name: 'web_search',
20
+ description: 'Search the web for information',
21
+ schema: Schema.Struct({
22
+ query: Schema.String.annotations({ description: 'The query to search for' }),
23
+ }),
24
+ execute: async ({ query }) => {
25
+ const context = await exa.searchAndContents(query, {
26
+ type: 'auto',
27
+ text: {
28
+ maxCharacters: 3_000,
29
+ },
30
+ livecrawl: 'always',
31
+ });
32
+
33
+ log.info('exa search', { query, costDollars: context.costDollars });
34
+
35
+ return ToolResult.Success(context);
36
+ },
37
+ });
38
+ };
@@ -0,0 +1,64 @@
1
+ You are an expert research agent. Your job is to assist a user by conducting in-depth research using real-time web search.
2
+ You output the results in a structured format matching the schema provided.
3
+
4
+ You are equipped with the ability to:
5
+
6
+ - Generate precise and effective search queries.
7
+ - Request web pages by query (through a `web_search` tool).
8
+ - Read the full content of retrieved pages.
9
+ - Synthesize accurate, clear, and structured answers using reliable information from the retrieved content.
10
+
11
+ Always follow these principles:
12
+
13
+ - Relevance First: Only return facts that are supported by content in the retrieved web pages. Never fabricate or guess information.
14
+ - Summarize, Don't Copy: Synthesize and rephrase content in your own words. Quote only when necessary.
15
+ - Multiple Sources: Cross-reference at least 2 sources before drawing conclusions, unless the information is directly stated and non-controversial.
16
+ - Transparency: Mention which sources were used and explain how you arrived at your conclusions.
17
+ - Accuracy Over Brevity: Prefer detailed, technically accurate explanations over shallow summaries.
18
+ - If uncertain, say so. It's better to admit uncertainty than to mislead.
19
+ - Pick the most concrete schema types for the extracted information.
20
+ - Fill the schema fields as completely as possible with the information you are confident about, do not fill the fields that you are not confident about.
21
+ - When outputting results, you can add extra data that isn't directly related to the user's question but fits the schema.
22
+
23
+ The user may ask for:
24
+
25
+ - Technical explanations
26
+ - Literature reviews
27
+ - Comparisons
28
+ - Emerging trends
29
+ - Implementation strategies
30
+
31
+ Begin by interpreting the user's request, then:
32
+
33
+ Break it into sub-questions (if applicable).
34
+
35
+ For each sub-question, generate a clear, concise web search query.
36
+
37
+ Use `web_search`(query) to retrieve information.
38
+
39
+ Extract and synthesize relevant answers.
40
+
41
+ Output should include:
42
+
43
+ - A clear, structured answer to the user’s question.
44
+ - A citation list or link list of the sources used.
45
+
46
+ Optionally, follow-up suggestions or questions for deeper inquiry.
47
+
48
+ Here's your operational instructions:
49
+
50
+ 1. Analyze user's request and identify key topics to search for (3 or more). Print them out.
51
+ 2. Perform a web search for each topic.
52
+ 3. Read and analyze the results, cross reference information from multiple sources, in case of conflict, represent the information as a range of possible values.
53
+ 4. Create a list of follow-up topics to research. Print them out.
54
+ 5. Perform a web search for each follow-up topic.
55
+ 6. Read and analyze the results, cross reference information from multiple sources, in case of conflict, represent the information as a range of possible values.
56
+ 9. Create a clear, structured answer to the user's question.
57
+ 10. Create a citation list or link list of the sources used.
58
+ 11. Select the most concrete schema types for the extracted information. You can use multiple different schema types. Print your decision and reasoning.
59
+ 12. Submit the results using the specific schema.
60
+
61
+
62
+
63
+
64
+
@@ -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
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './ai-service';
6
- export * from './cypher';
7
- export * from './conversation';
8
6
  export * from './session';
7
+
8
+ // TODO(dmaretskyi): Separate entrypoint for experimental features.
9
+ export * from './experimental';
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './session';
6
6
  export * from './version-pin';
7
+ export * from './tools';
@@ -3,17 +3,9 @@
3
3
  //
4
4
 
5
5
  import { Schema } from 'effect';
6
- import { inspect } from 'node:util';
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
10
  import { Type } from '@dxos/echo';
19
11
  import { create, ObjectId } from '@dxos/echo-schema';
@@ -21,7 +13,7 @@ import { DXN } from '@dxos/keys';
21
13
  import { log } from '@dxos/log';
22
14
 
23
15
  import { AISession } from './session';
24
- import { AI_SERVICE_ENDPOINT } from '../testing';
16
+ import { AI_SERVICE_ENDPOINT, ConsolePrinter } from '../testing';
25
17
 
26
18
  // Define a calendar event artifact schema.
27
19
  const CalendarEventSchema = Schema.Struct({
@@ -133,9 +125,10 @@ describe.skip('AISession with Ollama', () => {
133
125
  // printStreamEvent(event);
134
126
  // });
135
127
 
136
- session.message.on(printMessage);
137
- session.userMessage.on(printMessage);
138
- session.block.on(printContentBlock);
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));
139
132
 
140
133
  // session.update.on((update) => {
141
134
  // log('update', { update });
@@ -195,34 +188,6 @@ const CALENDAR_EVENTS: CalendarEvent[] = [
195
188
  }),
196
189
  ];
197
190
 
198
- const printMessage = (message: Message) => {
199
- console.log(`${message.role.toUpperCase()}\n`);
200
- for (const content of message.content) {
201
- printContentBlock(content);
202
- }
203
- };
204
-
205
- const printContentBlock = (content: MessageContentBlock) => {
206
- switch (content.type) {
207
- case 'text':
208
- console.log(content.text);
209
- break;
210
- case 'tool_use':
211
- console.log(`⚙️ [Tool Use] ${content.name} ${inspect(content.input, { depth: null, colors: true })}`);
212
- break;
213
- case 'tool_result': {
214
- let data: any;
215
- try {
216
- data = JSON.parse(content.content);
217
- } catch {
218
- data = content.content;
219
- }
220
- console.log(`⚙️ [Tool Result] ${content.toolUseId} ${inspect(data, { depth: null, colors: true })}`);
221
- break;
222
- }
223
- }
224
- };
225
-
226
191
  // const printStreamEvent = (event: GenerationStreamEvent) => {
227
192
  // switch (event.type) {
228
193
  // case 'message_start': {
@@ -7,6 +7,7 @@ import { Option, Schema } from 'effect';
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.
@@ -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,
@@ -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 & {
@@ -8,3 +8,4 @@ export * from './ollama';
8
8
  export * from './query-promts';
9
9
  export * from './test-data';
10
10
  export * from './test-schema';
11
+ export * from './printer';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import type { ImageSource, MessageContentBlock } from '@dxos/artifact';
6
6
 
7
- import type { ConversationEvent } from '../conversation';
7
+ import type { ConversationEvent } from '../experimental';
8
8
 
9
9
  export const createLogger = ({
10
10
  stream,
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { inspect } from 'node:util';
6
+
7
+ import type { Message, MessageContentBlock } from '@dxos/artifact';
8
+
9
+ export class ConsolePrinter {
10
+ printMessage = (message: Message) => {
11
+ // eslint-disable-next-line no-console
12
+ console.log(`${message.role.toUpperCase()}\n`);
13
+ for (const content of message.content) {
14
+ this.printContentBlock(content);
15
+ }
16
+ };
17
+
18
+ printContentBlock = (content: MessageContentBlock) => {
19
+ switch (content.type) {
20
+ case 'text':
21
+ // eslint-disable-next-line no-console
22
+ console.log(content.text);
23
+ break;
24
+ case 'tool_use':
25
+ // eslint-disable-next-line no-console
26
+ console.log(`⚙️ [Tool Use] ${content.name} ${inspect(content.input, { depth: null, colors: true })}`);
27
+ break;
28
+ case 'tool_result': {
29
+ let data: any;
30
+ try {
31
+ data = JSON.parse(content.content);
32
+ } catch {
33
+ data = content.content;
34
+ }
35
+ // eslint-disable-next-line no-console
36
+ console.log(`⚙️ [Tool Result] ${content.toolUseId} ${inspect(data, { depth: null, colors: true })}`);
37
+ break;
38
+ }
39
+ }
40
+ };
41
+ }