@dxos/assistant-toolkit 0.8.4-main.ae835ea → 0.8.4-main.e8ec1fe

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 (96) hide show
  1. package/dist/lib/browser/index.mjs +891 -595
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +890 -595
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/blueprints/research/research-blueprint.d.ts.map +1 -1
  8. package/dist/types/src/blueprints/websearch/websearch-toolkit.d.ts +1 -0
  9. package/dist/types/src/blueprints/websearch/websearch-toolkit.d.ts.map +1 -1
  10. package/dist/types/src/crud/graph.d.ts.map +1 -0
  11. package/dist/types/src/crud/graph.test.d.ts.map +1 -0
  12. package/dist/types/src/crud/index.d.ts +2 -0
  13. package/dist/types/src/crud/index.d.ts.map +1 -0
  14. package/dist/types/src/functions/agent/prompt.d.ts +3 -5
  15. package/dist/types/src/functions/agent/prompt.d.ts.map +1 -1
  16. package/dist/types/src/functions/discord/fetch-messages.d.ts +1 -1
  17. package/dist/types/src/functions/discord/index.d.ts +1 -1
  18. package/dist/types/src/functions/discord/index.d.ts.map +1 -1
  19. package/dist/types/src/functions/document/index.d.ts +3 -2
  20. package/dist/types/src/functions/document/index.d.ts.map +1 -1
  21. package/dist/types/src/functions/document/read.d.ts +1 -1
  22. package/dist/types/src/functions/document/update.d.ts +1 -1
  23. package/dist/types/src/functions/entity-extraction/entity-extraction.d.ts +2 -2
  24. package/dist/types/src/functions/entity-extraction/entity-extraction.d.ts.map +1 -1
  25. package/dist/types/src/functions/entity-extraction/index.d.ts +2 -2
  26. package/dist/types/src/functions/entity-extraction/index.d.ts.map +1 -1
  27. package/dist/types/src/functions/exa/exa.d.ts +1 -1
  28. package/dist/types/src/functions/exa/mock.d.ts +1 -1
  29. package/dist/types/src/functions/github/fetch-prs.d.ts +1 -1
  30. package/dist/types/src/functions/linear/index.d.ts +1 -1
  31. package/dist/types/src/functions/linear/index.d.ts.map +1 -1
  32. package/dist/types/src/functions/linear/sync-issues.d.ts +1 -1
  33. package/dist/types/src/functions/research/document-create.d.ts +9 -0
  34. package/dist/types/src/functions/research/document-create.d.ts.map +1 -0
  35. package/dist/types/src/functions/research/index.d.ts +8 -6
  36. package/dist/types/src/functions/research/index.d.ts.map +1 -1
  37. package/dist/types/src/functions/research/research.d.ts +4 -3
  38. package/dist/types/src/functions/research/research.d.ts.map +1 -1
  39. package/dist/types/src/functions/research/types.d.ts +2 -380
  40. package/dist/types/src/functions/research/types.d.ts.map +1 -1
  41. package/dist/types/src/functions/tasks/index.d.ts +2 -2
  42. package/dist/types/src/functions/tasks/index.d.ts.map +1 -1
  43. package/dist/types/src/functions/tasks/read.d.ts +1 -1
  44. package/dist/types/src/functions/tasks/update.d.ts +1 -1
  45. package/dist/types/src/index.d.ts +2 -0
  46. package/dist/types/src/index.d.ts.map +1 -1
  47. package/dist/types/src/toolkits/AssistantToolkit.d.ts +17 -0
  48. package/dist/types/src/toolkits/AssistantToolkit.d.ts.map +1 -0
  49. package/dist/types/src/toolkits/AssistantToolkit.test.d.ts +2 -0
  50. package/dist/types/src/toolkits/AssistantToolkit.test.d.ts.map +1 -0
  51. package/dist/types/src/toolkits/SystemToolkit.d.ts +67 -0
  52. package/dist/types/src/toolkits/SystemToolkit.d.ts.map +1 -0
  53. package/dist/types/src/toolkits/index.d.ts +3 -0
  54. package/dist/types/src/toolkits/index.d.ts.map +1 -0
  55. package/dist/types/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +24 -22
  57. package/src/blueprints/design/design-blueprint.test.ts +9 -14
  58. package/src/blueprints/design/design-blueprint.ts +2 -2
  59. package/src/blueprints/discord/discord-blueprint.ts +2 -2
  60. package/src/blueprints/linear/linear-blueprint.ts +2 -2
  61. package/src/blueprints/planning/planning-blueprint.test.ts +11 -16
  62. package/src/blueprints/planning/planning-blueprint.ts +2 -2
  63. package/src/blueprints/research/research-blueprint.ts +23 -15
  64. package/src/blueprints/websearch/websearch-blueprint.ts +2 -2
  65. package/src/{functions/research → crud}/graph.test.ts +2 -2
  66. package/src/crud/index.ts +5 -0
  67. package/src/experimental/feed.test.ts +11 -8
  68. package/src/functions/agent/prompt.ts +25 -12
  69. package/src/functions/discord/fetch-messages.test.ts +5 -6
  70. package/src/functions/discord/fetch-messages.ts +10 -9
  71. package/src/functions/document/index.ts +1 -0
  72. package/src/functions/entity-extraction/entity-extraction.conversations.json +1 -1
  73. package/src/functions/entity-extraction/entity-extraction.test.ts +11 -19
  74. package/src/functions/entity-extraction/entity-extraction.ts +15 -13
  75. package/src/functions/linear/linear.test.ts +13 -16
  76. package/src/functions/linear/sync-issues.ts +7 -7
  77. package/src/functions/research/{create-document.ts → document-create.ts} +32 -26
  78. package/src/functions/research/index.ts +1 -2
  79. package/src/functions/research/{instructions-research.tpl → research-instructions.tpl} +14 -6
  80. package/src/functions/research/research.conversations.json +1 -10714
  81. package/src/functions/research/research.test.ts +87 -148
  82. package/src/functions/research/research.ts +84 -49
  83. package/src/functions/research/types.ts +14 -12
  84. package/src/index.ts +2 -0
  85. package/src/toolkits/AssistantToolkit.conversations.json +1 -0
  86. package/src/toolkits/AssistantToolkit.test.ts +88 -0
  87. package/src/toolkits/AssistantToolkit.ts +47 -0
  88. package/src/toolkits/SystemToolkit.ts +231 -0
  89. package/src/toolkits/index.ts +6 -0
  90. package/dist/types/src/functions/research/create-document.d.ts +0 -7
  91. package/dist/types/src/functions/research/create-document.d.ts.map +0 -1
  92. package/dist/types/src/functions/research/graph.d.ts.map +0 -1
  93. package/dist/types/src/functions/research/graph.test.d.ts.map +0 -1
  94. /package/dist/types/src/{functions/research → crud}/graph.d.ts +0 -0
  95. /package/dist/types/src/{functions/research → crud}/graph.test.d.ts +0 -0
  96. /package/src/{functions/research → crud}/graph.ts +0 -0
@@ -9,7 +9,7 @@ import * as Effect from 'effect/Effect';
9
9
  import * as Layer from 'effect/Layer';
10
10
 
11
11
  import { AiService, ConsolePrinter, MemoizedAiService } from '@dxos/ai';
12
- import { AiServiceTestingPreset, EXA_API_KEY } from '@dxos/ai/testing';
12
+ import { TestAiService } from '@dxos/ai/testing';
13
13
  import {
14
14
  AiConversation,
15
15
  type ContextBinding,
@@ -18,25 +18,26 @@ import {
18
18
  makeToolResolverFromFunctions,
19
19
  } from '@dxos/assistant';
20
20
  import { Blueprint } from '@dxos/blueprints';
21
- import { Obj, Ref, Type } from '@dxos/echo';
21
+ import { Filter, Obj, Query, Ref } from '@dxos/echo';
22
22
  import { TestHelpers, acquireReleaseResource } from '@dxos/effect';
23
23
  import {
24
- ComputeEventLogger,
25
24
  CredentialsService,
26
25
  DatabaseService,
27
26
  FunctionInvocationService,
28
27
  QueueService,
29
28
  TracingService,
30
29
  } from '@dxos/functions';
31
- import { TestDatabaseLayer } from '@dxos/functions/testing';
30
+ import { FunctionInvocationServiceLayerTest, TestDatabaseLayer } from '@dxos/functions-runtime/testing';
31
+ import { invariant } from '@dxos/invariant';
32
32
  import { ObjectId } from '@dxos/keys';
33
- import { DataType } from '@dxos/schema';
33
+ import { MarkdownBlueprint, MarkdownFunction } from '@dxos/plugin-markdown/toolkit';
34
+ import { Markdown } from '@dxos/plugin-markdown/types';
35
+ import { HasSubject, type Message, Organization } from '@dxos/types';
34
36
 
35
37
  import { ResearchBlueprint } from '../../blueprints';
36
38
  import { testToolkit } from '../../blueprints/testing';
37
39
 
38
- import createDocument from './create-document';
39
- import { createExtractionSchema, getSanitizedSchemaName } from './graph';
40
+ import { default as createDocument } from './document-create';
40
41
  import { default as research } from './research';
41
42
  import { ResearchGraph, queryResearchGraph } from './research-graph';
42
43
  import { ResearchDataTypes } from './types';
@@ -45,41 +46,45 @@ ObjectId.dangerouslyDisableRandomness();
45
46
 
46
47
  const TestLayer = Layer.mergeAll(
47
48
  AiService.model('@anthropic/claude-opus-4-0'),
48
- makeToolResolverFromFunctions([research, createDocument], testToolkit),
49
+ makeToolResolverFromFunctions(
50
+ [research, createDocument, MarkdownFunction.create, MarkdownFunction.open, MarkdownFunction.update],
51
+ testToolkit,
52
+ ),
49
53
  makeToolExecutionServiceFromFunctions(testToolkit, testToolkit.toLayer({}) as any),
50
- ComputeEventLogger.layerFromTracing,
51
54
  ).pipe(
52
- Layer.provideMerge(FunctionInvocationService.layerTest({ functions: [research, createDocument] })),
55
+ Layer.provideMerge(
56
+ FunctionInvocationServiceLayerTest({
57
+ functions: [research, createDocument, MarkdownFunction.create, MarkdownFunction.open, MarkdownFunction.update],
58
+ }),
59
+ ),
53
60
  Layer.provideMerge(
54
61
  Layer.mergeAll(
55
- MemoizedAiService.layerTest().pipe(Layer.provide(AiServiceTestingPreset('direct'))),
62
+ TestAiService(),
56
63
  TestDatabaseLayer({
64
+ spaceKey: 'fixed',
57
65
  indexing: { vector: true },
58
- types: [...ResearchDataTypes, ResearchGraph, Blueprint.Blueprint],
66
+ types: [...ResearchDataTypes, ResearchGraph, Blueprint.Blueprint, Markdown.Document, HasSubject.HasSubject],
59
67
  }),
60
- CredentialsService.configuredLayer([{ service: 'exa.ai', apiKey: EXA_API_KEY }]),
68
+ CredentialsService.configuredLayer([]),
61
69
  TracingService.layerNoop,
62
70
  ),
63
71
  ),
64
72
  );
65
73
 
66
- // TODO(dmaretskyi): Out-of-memory.
67
- describe.skip('Research', () => {
74
+ describe('Research', () => {
68
75
  it.effect(
69
76
  'call a function to generate a research report',
70
77
  Effect.fnUntraced(
71
78
  function* (_) {
72
79
  yield* DatabaseService.add(
73
- Obj.make(DataType.Organization, {
74
- name: 'Airbnb',
75
- website: 'https://www.airbnb.com/',
80
+ Obj.make(Organization.Organization, {
81
+ name: 'BlueYard',
82
+ website: 'https://blueyard.com',
76
83
  }),
77
84
  );
78
85
  yield* DatabaseService.flush({ indexes: true });
79
-
80
86
  const result = yield* FunctionInvocationService.invokeFunction(research, {
81
- query: 'Founders and investors of airbnb.',
82
- mockSearch: false,
87
+ query: 'Founders and portfolio of BlueYard.',
83
88
  });
84
89
 
85
90
  console.log(inspect(result, { depth: null, colors: true }));
@@ -87,154 +92,88 @@ describe.skip('Research', () => {
87
92
 
88
93
  yield* DatabaseService.flush({ indexes: true });
89
94
  const researchGraph = yield* queryResearchGraph();
90
- const data = yield* DatabaseService.load(researchGraph!.queue).pipe(
91
- Effect.flatMap((queue) => Effect.promise(() => queue.queryObjects())),
92
- );
93
- console.log(inspect(data, { depth: null, colors: true }));
95
+ if (researchGraph) {
96
+ const data = yield* DatabaseService.load(researchGraph.queue).pipe(
97
+ Effect.flatMap((queue) => Effect.promise(() => queue.queryObjects())),
98
+ );
99
+ console.log(inspect(data, { depth: null, colors: true }));
100
+ }
94
101
  },
95
102
  Effect.provide(TestLayer),
96
103
  TestHelpers.provideTestContext,
97
104
  ),
98
- MemoizedAiService.isGenerationEnabled() ? 240_000 : undefined,
105
+ MemoizedAiService.isGenerationEnabled() ? 240_000 : 30_000,
99
106
  );
100
107
 
101
108
  it.scoped(
102
- 'research blueprint',
109
+ 'create and update research report',
103
110
  Effect.fnUntraced(
104
111
  function* (_) {
105
- const queue = yield* QueueService.createQueue<DataType.Message | ContextBinding>();
112
+ const organization = yield* DatabaseService.add(
113
+ Obj.make(Organization.Organization, {
114
+ name: 'BlueYard',
115
+ website: 'https://blueyard.com',
116
+ }),
117
+ );
118
+
119
+ const queue = yield* QueueService.createQueue<Message.Message | ContextBinding>();
106
120
  const conversation = yield* acquireReleaseResource(() => new AiConversation(queue));
107
121
 
108
- const org = Obj.make(DataType.Organization, { name: 'Airbnb', website: 'https://www.airbnb.com/' });
109
- yield* DatabaseService.add(org);
110
122
  yield* DatabaseService.flush({ indexes: true });
111
-
112
- const blueprint = yield* DatabaseService.add(Obj.clone(ResearchBlueprint));
113
- yield* Effect.promise(() => conversation.context.bind({ blueprints: [Ref.make(blueprint)] }));
123
+ const researchBlueprint = yield* DatabaseService.add(Obj.clone(ResearchBlueprint));
124
+ const markdownBlueprint = yield* DatabaseService.add(Obj.clone(MarkdownBlueprint));
125
+ yield* Effect.promise(() =>
126
+ conversation.context.bind({
127
+ blueprints: [Ref.make(researchBlueprint), Ref.make(markdownBlueprint)],
128
+ objects: [Ref.make(organization)],
129
+ }),
130
+ );
114
131
 
115
132
  const observer = GenerationObserver.fromPrinter(new ConsolePrinter());
133
+
116
134
  yield* conversation.createRequest({
117
135
  observer,
118
- prompt: `Research airbnb founders.`,
136
+ prompt: `Create a research summary about ${organization.name}.`,
119
137
  });
138
+ {
139
+ const { objects: docs } = yield* DatabaseService.runQuery(
140
+ Query.select(Filter.ids(organization.id)).targetOf(HasSubject.HasSubject).source(),
141
+ );
142
+ if (docs.length !== 1) {
143
+ throw new Error(`Expected 1 research document; got ${docs.length}: ${docs.map((_) => _.name)}`);
144
+ }
145
+
146
+ const doc = docs[0];
147
+ invariant(Obj.instanceOf(Markdown.Document, doc));
148
+ console.log({
149
+ name: doc.name,
150
+ content: yield* DatabaseService.load(doc.content).pipe(Effect.map((_) => _.content)),
151
+ });
152
+ }
120
153
 
121
- const researchGraph = yield* queryResearchGraph();
122
- const data = yield* DatabaseService.load(researchGraph!.queue).pipe(
123
- Effect.flatMap((queue) => Effect.promise(() => queue.queryObjects())),
124
- );
125
- console.log(inspect(data, { depth: null, colors: true }));
154
+ yield* conversation.createRequest({
155
+ observer,
156
+ prompt: 'Add a section about their portfolio.',
157
+ });
158
+ {
159
+ const { objects: docs } = yield* DatabaseService.runQuery(
160
+ Query.select(Filter.ids(organization.id)).targetOf(HasSubject.HasSubject).source(),
161
+ );
162
+ if (docs.length !== 1) {
163
+ throw new Error(`Expected 1 research document; got ${docs.length}: ${docs.map((_) => _.name)}`);
164
+ }
165
+
166
+ const doc = docs[0];
167
+ invariant(Obj.instanceOf(Markdown.Document, doc));
168
+ console.log({
169
+ name: doc.name,
170
+ content: yield* DatabaseService.load(doc.content).pipe(Effect.map((_) => _.content)),
171
+ });
172
+ }
126
173
  },
127
174
  Effect.provide(TestLayer),
128
175
  TestHelpers.provideTestContext,
129
176
  ),
130
- MemoizedAiService.isGenerationEnabled() ? 240_000 : undefined,
177
+ MemoizedAiService.isGenerationEnabled() ? 240_000 : 30_000,
131
178
  );
132
179
  });
133
-
134
- describe('misc', () => {
135
- it('createExtractionSchema', () => {
136
- const _schema = createExtractionSchema(ResearchDataTypes);
137
- // log.info('schema', { schema });
138
- });
139
-
140
- it('getSanitizedSchemaName', () => {
141
- const _names = ResearchDataTypes.map(getSanitizedSchemaName);
142
- // log.info('names', { names }) ;
143
- });
144
-
145
- it('getTypeAnnotation', () => {
146
- for (const schema of ResearchDataTypes) {
147
- const _dxn = Type.getDXN(schema);
148
- // log.info('dxn', { schema, dxn });
149
- }
150
- });
151
-
152
- it.skip('sanitizeObjects', () => {
153
- const _TEST_DATA = {
154
- objects_dxos_org_type_Project: [
155
- {
156
- name: 'Reor',
157
- description:
158
- '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.',
159
- image: 'https://reorhomepage-2-cwy0zagzg-reor-team.vercel.app/opengraph-image.jpg',
160
- id: 'reor-project-01',
161
- },
162
- {
163
- name: 'Personal AI',
164
- description:
165
- '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.',
166
- image: 'https://cdn.prod.website-files.com/5ff65c460ce39f5ec5681c6a/663d12aab1b425e1ad40d3a6_Memory-min.jpg',
167
- id: 'personal-ai-project-01',
168
- },
169
- {
170
- name: 'IKI AI',
171
- description:
172
- 'An AI-native workspace for research, strategy, and creative work. Offers features like AI summarization, content analysis, and team knowledge sharing capabilities.',
173
- image: 'https://framerusercontent.com/assets/cI6Uo7x4q0W3uxzOt2preXjv6aE.jpg',
174
- id: 'iki-ai-project-01',
175
- },
176
- {
177
- name: 'Forethink',
178
- description:
179
- '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.',
180
- image: 'https://framerusercontent.com/assets/XDhN6tICnlElB134JWcWXOuXc.jpeg',
181
- id: 'forethink-project-01',
182
- },
183
- {
184
- name: 'Amurex',
185
- description:
186
- 'An invisible AI companion that handles tasks, remembers important information, and supports workflow. Includes features for meeting automation, email categorization, and cross-platform search.',
187
- image: 'https://amurex.ai/ogimages/og_amurex.jpg',
188
- id: 'amurex-project-01',
189
- },
190
- ],
191
- objects_dxos_org_type_Organization: [
192
- {
193
- name: 'Reor',
194
- description:
195
- 'A technology company focused on developing local-first AI solutions for personal knowledge management.',
196
- status: 'active',
197
- website: 'https://www.reorproject.org',
198
- id: 'reor-org-01',
199
- },
200
- {
201
- name: 'Personal.ai',
202
- description: 'A company specializing in personal AI development and memory management solutions.',
203
- status: 'active',
204
- website: 'https://www.personal.ai',
205
- id: 'personal-ai-org-01',
206
- },
207
- {
208
- name: 'IKI AI',
209
- description: 'A startup backed by 500 Global, developing AI-native workspaces for knowledge workers.',
210
- status: 'active',
211
- website: 'https://iki.ai',
212
- id: 'iki-ai-org-01',
213
- },
214
- {
215
- name: 'Forethink.ai',
216
- description:
217
- 'A company focused on privacy-first AI solutions for knowledge management and team collaboration.',
218
- status: 'active',
219
- website: 'https://www.forethink.ai',
220
- id: 'forethink-org-01',
221
- },
222
- ],
223
- objects_dxos_org_type_Text: [
224
- {
225
- content:
226
- '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.',
227
- id: 'pkm-trends-text-01',
228
- },
229
- {
230
- content:
231
- '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.',
232
- id: 'pkm-features-text-01',
233
- },
234
- ],
235
- };
236
-
237
- // const data = await sanitizeObjects(TYPES, TEST_DATA, db);
238
- // log.info('data', { data });
239
- });
240
- });
@@ -6,8 +6,11 @@ import * as Toolkit from '@effect/ai/Toolkit';
6
6
  import * as AnthropicTool from '@effect/ai-anthropic/AnthropicTool';
7
7
  import * as Array from 'effect/Array';
8
8
  import * as Effect from 'effect/Effect';
9
+ import * as Function from 'effect/Function';
9
10
  import * as Layer from 'effect/Layer';
11
+ import * as Option from 'effect/Option';
10
12
  import * as Schema from 'effect/Schema';
13
+ import * as String from 'effect/String';
11
14
 
12
15
  import { AiService, ConsolePrinter } from '@dxos/ai';
13
16
  import {
@@ -17,17 +20,18 @@ import {
17
20
  makeToolExecutionServiceFromFunctions,
18
21
  makeToolResolverFromFunctions,
19
22
  } from '@dxos/assistant';
23
+ import { Template } from '@dxos/blueprints';
20
24
  import { type DXN, Obj } from '@dxos/echo';
21
- import { DatabaseService, FunctionInvocationService, TracingService, defineFunction } from '@dxos/functions';
22
- import { DataType } from '@dxos/schema';
25
+ import { DatabaseService, TracingService, defineFunction } from '@dxos/functions';
26
+ import { FunctionInvocationServiceLayerTestMocked } from '@dxos/functions-runtime/testing';
27
+ import { type Message, Person } from '@dxos/types';
23
28
  import { trim } from '@dxos/util';
24
29
 
30
+ import { LocalSearchHandler, LocalSearchToolkit, makeGraphWriterHandler, makeGraphWriterToolkit } from '../../crud';
25
31
  import { exaFunction, exaMockFunction } from '../exa';
26
32
 
27
- import { LocalSearchHandler, LocalSearchToolkit, makeGraphWriterHandler, makeGraphWriterToolkit } from './graph';
28
- // TODO(dmaretskyi): Vite build bug with instruction files with the same filename getting mixed-up.
29
- import PROMPT from './instructions-research.tpl?raw';
30
33
  import { contextQueueLayerFromResearchGraph } from './research-graph';
34
+ import PROMPT from './research-instructions.tpl?raw';
31
35
  import { ResearchDataTypes } from './types';
32
36
 
33
37
  /**
@@ -37,22 +41,21 @@ export default defineFunction({
37
41
  key: 'dxos.org/function/research',
38
42
  name: 'Research',
39
43
  description: trim`
40
- Research the web for information.
44
+ Search the web to research information about the given subject.
41
45
  Inserts structured data into the research graph.
42
- Will return research summary and the objects created.
46
+ Creates a research summary and returns the objects created.
43
47
  `,
44
48
  inputSchema: Schema.Struct({
45
49
  query: Schema.String.annotations({
46
50
  description: trim`
47
- The query to search for.
48
- If doing research on an object, load it first and pass it as a JSON string.
51
+ The search query.
52
+ If doing research on an object then load it first and pass it as JSON.
49
53
  `,
50
54
  }),
51
55
 
52
- researchInstructions: Schema.optional(Schema.String).annotations({
56
+ instructions: Schema.optional(Schema.String).annotations({
53
57
  description: trim`
54
58
  The instructions for the research agent.
55
- E.g., preference on fast responses or in-depth analysis, number of web searcher or the objects created.
56
59
  `,
57
60
  }),
58
61
 
@@ -61,20 +64,28 @@ export default defineFunction({
61
64
  description: 'Whether to use the mock search tool.',
62
65
  default: false,
63
66
  }),
67
+
68
+ entityExtraction: Schema.optional(Schema.Boolean).annotations({
69
+ description: trim`
70
+ Whether to extract structured entities from the research.
71
+ Experimental feature only enable if user explicitly requests it.
72
+ `,
73
+ default: false,
74
+ }),
64
75
  }),
65
76
  outputSchema: Schema.Struct({
66
- note: Schema.optional(Schema.String).annotations({
67
- description: 'A note from the research agent.',
77
+ document: Schema.optional(Schema.String).annotations({
78
+ description: 'The generated research document.',
68
79
  }),
69
80
  objects: Schema.Array(Schema.Unknown).annotations({
70
- description: 'The structured objects created as a result of the research.',
81
+ description: 'Structured objects created during the research process.',
71
82
  }),
72
83
  }),
73
84
  handler: Effect.fnUntraced(
74
- function* ({ data: { query, mockSearch, researchInstructions } }) {
85
+ function* ({ data: { query, instructions, mockSearch = false, entityExtraction = false } }) {
75
86
  if (mockSearch) {
76
87
  const mockPerson = yield* DatabaseService.add(
77
- Obj.make(DataType.Person, {
88
+ Obj.make(Person.Person, {
78
89
  preferredName: 'John Doe',
79
90
  emails: [{ value: 'john.doe@example.com' }],
80
91
  phoneNumbers: [{ value: '123-456-7890' }],
@@ -82,9 +93,8 @@ export default defineFunction({
82
93
  );
83
94
 
84
95
  return {
85
- note: trim`
86
- The research run in test-mode and was mocked.
87
- Proceed as usual.
96
+ document: trim`
97
+ The research ran in test-mode and was mocked. Proceed as usual.
88
98
  We reference John Doe to test reference: ${Obj.getDXN(mockPerson)}
89
99
  `,
90
100
  objects: [Obj.toJSON(mockPerson)],
@@ -92,49 +102,47 @@ export default defineFunction({
92
102
  }
93
103
 
94
104
  yield* DatabaseService.flush({ indexes: true });
95
- yield* TracingService.emitStatus({ message: 'Researching...' });
105
+ yield* TracingService.emitStatus({ message: 'Starting research...' });
96
106
 
97
- const objectDXNs: DXN[] = [];
98
- const GraphWriterToolkit = makeGraphWriterToolkit({ schema: ResearchDataTypes });
99
- const GraphWriterHandler = makeGraphWriterHandler(GraphWriterToolkit, {
100
- onAppend: (dxns) => objectDXNs.push(...dxns),
101
- });
102
107
  const NativeWebSearch = Toolkit.make(AnthropicTool.WebSearch_20250305({}));
103
108
 
104
- const toolkit = yield* createToolkit({
105
- toolkit: Toolkit.merge(LocalSearchToolkit, GraphWriterToolkit, NativeWebSearch),
106
- // toolIds: [mockSearch ? ToolId.make(exaMockFunction.key) : ToolId.make(exaFunction.key)],
107
- }).pipe(
108
- Effect.provide(
109
- Layer.mergeAll(
110
- //
111
- GraphWriterHandler,
112
- LocalSearchHandler,
113
- ).pipe(Layer.provide(contextQueueLayerFromResearchGraph)),
114
- ),
115
- );
109
+ let toolkit: Toolkit.Any = NativeWebSearch;
110
+ let handlers: Layer.Layer<any, any> = Layer.empty as any;
111
+
112
+ const objectDXNs: DXN[] = [];
113
+ if (entityExtraction) {
114
+ const GraphWriterToolkit = makeGraphWriterToolkit({ schema: ResearchDataTypes });
115
+ const GraphWriterHandler = makeGraphWriterHandler(GraphWriterToolkit, {
116
+ onAppend: (dxns) => objectDXNs.push(...dxns),
117
+ });
118
+
119
+ toolkit = Toolkit.merge(toolkit, LocalSearchToolkit, GraphWriterToolkit);
120
+ handlers = Layer.mergeAll(handlers, LocalSearchHandler, GraphWriterHandler).pipe(
121
+ Layer.provide(contextQueueLayerFromResearchGraph),
122
+ ) as any;
123
+ }
124
+
125
+ const finishedToolkit = yield* createToolkit({
126
+ toolkit: toolkit as any,
127
+ }).pipe(Effect.provide(handlers));
116
128
 
117
129
  const session = new AiSession();
118
130
  const result = yield* session.run({
119
131
  prompt: query,
120
- system:
121
- PROMPT +
122
- (researchInstructions
123
- ? '\n\n' + `<research_instructions>${researchInstructions}</research_instructions>`
124
- : ''),
125
- toolkit,
132
+ system: join(
133
+ Template.process(PROMPT, { entityExtraction }),
134
+ instructions && `<instructions>${instructions}</instructions>`,
135
+ ),
136
+ toolkit: finishedToolkit,
126
137
  observer: GenerationObserver.fromPrinter(new ConsolePrinter({ tag: 'research' })),
127
138
  });
128
- const note = result
129
- .at(-1)
130
- ?.blocks.filter((block) => block._tag === 'text')
131
- .at(-1)?.text;
139
+
132
140
  const objects = yield* Effect.forEach(objectDXNs, (dxn) => DatabaseService.resolve(dxn)).pipe(
133
141
  Effect.map(Array.map((obj) => Obj.toJSON(obj))),
134
142
  );
135
143
 
136
144
  return {
137
- note,
145
+ document: extractLastTextBlock(result),
138
146
  objects,
139
147
  };
140
148
  },
@@ -147,9 +155,36 @@ export default defineFunction({
147
155
  ).pipe(
148
156
  Layer.provide(
149
157
  // TODO(dmaretskyi): This should be provided by environment.
150
- Layer.mergeAll(FunctionInvocationService.layerTestMocked({ functions: [exaFunction, exaMockFunction] })),
158
+ Layer.mergeAll(FunctionInvocationServiceLayerTestMocked({ functions: [exaFunction, exaMockFunction] })),
151
159
  ),
152
160
  ),
153
161
  ),
154
162
  ),
155
163
  });
164
+
165
+ // TODO(burdon): Factor out.
166
+ const join = (...strings: (string | undefined)[]) => strings.filter(Boolean).join('\n\n');
167
+
168
+ /**
169
+ * Extracts the last text block from the result.
170
+ * Skips citations.
171
+ */
172
+ // TODO(burdon): Factor out.
173
+ const extractLastTextBlock = (result: Message.Message[]) => {
174
+ return Function.pipe(
175
+ result,
176
+ Array.last,
177
+ Option.map(
178
+ Function.flow(
179
+ (_: Message.Message) => _.blocks,
180
+ Array.reverse,
181
+ Array.dropWhile((_: any) => _._tag === 'summary'),
182
+ Array.takeWhile((_: any) => _._tag === 'text'),
183
+ Array.reverse,
184
+ Array.map((_: any) => _.text),
185
+ Array.reduce('', String.concat),
186
+ ),
187
+ ),
188
+ Option.getOrElse(() => ''),
189
+ );
190
+ };
@@ -2,23 +2,25 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { DataType } from '@dxos/schema';
5
+ import { type Type } from '@dxos/echo';
6
+ import { Text } from '@dxos/schema';
7
+ import { Event, HasConnection, HasRelationship, LegacyOrganization, LegacyPerson, Project, Task } from '@dxos/types';
6
8
 
7
9
  /**
8
10
  * Data types for research.
9
11
  */
10
- export const ResearchDataTypes = [
12
+ export const ResearchDataTypes: (Type.Obj.Any | Type.Relation.Any)[] = [
11
13
  // Objects
12
- DataType.Event,
13
- DataType.LegacyOrganization,
14
- DataType.LegacyPerson,
15
- DataType.Project,
16
- DataType.Task,
17
- DataType.Text,
14
+ Event.Event,
15
+ LegacyOrganization,
16
+ Task.Task,
17
+ Text.Text,
18
18
 
19
19
  // Relations
20
- // TODO(wittjosiah): Until views (e.g. table) support relations this needs to be expressed via organization ref.
21
- // DataType.Employer,
22
- DataType.HasRelationship,
23
- DataType.HasConnection,
20
+ // TODO(wittjosiah): Until views (e.g., Table) support relations this needs to be expressed via organization ref.
21
+ // Employer.Employer,
22
+ LegacyPerson,
23
+ Project.Project,
24
+ HasRelationship.HasRelationship,
25
+ HasConnection.HasConnection,
24
26
  ];
package/src/index.ts CHANGED
@@ -6,3 +6,5 @@ export * from './blueprints';
6
6
  export * from './functions';
7
7
  export * from './plugins';
8
8
  export * from './sync';
9
+ export * from './crud';
10
+ export * from './toolkits';