@dxos/assistant-toolkit 0.8.4-main.2c6827d

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 (216) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +3 -0
  3. package/dist/lib/browser/index.mjs +2778 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node-esm/index.mjs +2779 -0
  7. package/dist/lib/node-esm/index.mjs.map +7 -0
  8. package/dist/lib/node-esm/meta.json +1 -0
  9. package/dist/types/src/blueprints/design/design-blueprint.d.ts +4 -0
  10. package/dist/types/src/blueprints/design/design-blueprint.d.ts.map +1 -0
  11. package/dist/types/src/blueprints/design/design-blueprint.test.d.ts +2 -0
  12. package/dist/types/src/blueprints/design/design-blueprint.test.d.ts.map +1 -0
  13. package/dist/types/src/blueprints/design/index.d.ts +3 -0
  14. package/dist/types/src/blueprints/design/index.d.ts.map +1 -0
  15. package/dist/types/src/blueprints/discord/discord-blueprint.d.ts +18 -0
  16. package/dist/types/src/blueprints/discord/discord-blueprint.d.ts.map +1 -0
  17. package/dist/types/src/blueprints/discord/index.d.ts +3 -0
  18. package/dist/types/src/blueprints/discord/index.d.ts.map +1 -0
  19. package/dist/types/src/blueprints/index.d.ts +7 -0
  20. package/dist/types/src/blueprints/index.d.ts.map +1 -0
  21. package/dist/types/src/blueprints/linear/index.d.ts +3 -0
  22. package/dist/types/src/blueprints/linear/index.d.ts.map +1 -0
  23. package/dist/types/src/blueprints/linear/linear-blueprint.d.ts +18 -0
  24. package/dist/types/src/blueprints/linear/linear-blueprint.d.ts.map +1 -0
  25. package/dist/types/src/blueprints/planning/index.d.ts +3 -0
  26. package/dist/types/src/blueprints/planning/index.d.ts.map +1 -0
  27. package/dist/types/src/blueprints/planning/planning-blueprint.d.ts +4 -0
  28. package/dist/types/src/blueprints/planning/planning-blueprint.d.ts.map +1 -0
  29. package/dist/types/src/blueprints/planning/planning-blueprint.test.d.ts +2 -0
  30. package/dist/types/src/blueprints/planning/planning-blueprint.test.d.ts.map +1 -0
  31. package/dist/types/src/blueprints/research/index.d.ts +3 -0
  32. package/dist/types/src/blueprints/research/index.d.ts.map +1 -0
  33. package/dist/types/src/blueprints/research/research-blueprint.d.ts +4 -0
  34. package/dist/types/src/blueprints/research/research-blueprint.d.ts.map +1 -0
  35. package/dist/types/src/blueprints/research/research-blueprint.test.d.ts +2 -0
  36. package/dist/types/src/blueprints/research/research-blueprint.test.d.ts.map +1 -0
  37. package/dist/types/src/blueprints/testing.d.ts +12 -0
  38. package/dist/types/src/blueprints/testing.d.ts.map +1 -0
  39. package/dist/types/src/blueprints/websearch/index.d.ts +4 -0
  40. package/dist/types/src/blueprints/websearch/index.d.ts.map +1 -0
  41. package/dist/types/src/blueprints/websearch/websearch-blueprint.d.ts +4 -0
  42. package/dist/types/src/blueprints/websearch/websearch-blueprint.d.ts.map +1 -0
  43. package/dist/types/src/blueprints/websearch/websearch-toolkit.d.ts +27 -0
  44. package/dist/types/src/blueprints/websearch/websearch-toolkit.d.ts.map +1 -0
  45. package/dist/types/src/crud/graph.d.ts +64 -0
  46. package/dist/types/src/crud/graph.d.ts.map +1 -0
  47. package/dist/types/src/crud/graph.test.d.ts +2 -0
  48. package/dist/types/src/crud/graph.test.d.ts.map +1 -0
  49. package/dist/types/src/crud/index.d.ts +2 -0
  50. package/dist/types/src/crud/index.d.ts.map +1 -0
  51. package/dist/types/src/experimental/feed.test.d.ts +2 -0
  52. package/dist/types/src/experimental/feed.test.d.ts.map +1 -0
  53. package/dist/types/src/functions/agent/index.d.ts +5 -0
  54. package/dist/types/src/functions/agent/index.d.ts.map +1 -0
  55. package/dist/types/src/functions/agent/prompt.d.ts +9 -0
  56. package/dist/types/src/functions/agent/prompt.d.ts.map +1 -0
  57. package/dist/types/src/functions/discord/fetch-messages.d.ts +11 -0
  58. package/dist/types/src/functions/discord/fetch-messages.d.ts.map +1 -0
  59. package/dist/types/src/functions/discord/fetch-messages.test.d.ts +2 -0
  60. package/dist/types/src/functions/discord/fetch-messages.test.d.ts.map +1 -0
  61. package/dist/types/src/functions/discord/index.d.ts +12 -0
  62. package/dist/types/src/functions/discord/index.d.ts.map +1 -0
  63. package/dist/types/src/functions/document/index.d.ts +13 -0
  64. package/dist/types/src/functions/document/index.d.ts.map +1 -0
  65. package/dist/types/src/functions/document/read.d.ts +7 -0
  66. package/dist/types/src/functions/document/read.d.ts.map +1 -0
  67. package/dist/types/src/functions/document/update.d.ts +6 -0
  68. package/dist/types/src/functions/document/update.d.ts.map +1 -0
  69. package/dist/types/src/functions/entity-extraction/entity-extraction.d.ts +173 -0
  70. package/dist/types/src/functions/entity-extraction/entity-extraction.d.ts.map +1 -0
  71. package/dist/types/src/functions/entity-extraction/entity-extraction.test.d.ts +2 -0
  72. package/dist/types/src/functions/entity-extraction/entity-extraction.test.d.ts.map +1 -0
  73. package/dist/types/src/functions/entity-extraction/index.d.ts +174 -0
  74. package/dist/types/src/functions/entity-extraction/index.d.ts.map +1 -0
  75. package/dist/types/src/functions/exa/exa.d.ts +5 -0
  76. package/dist/types/src/functions/exa/exa.d.ts.map +1 -0
  77. package/dist/types/src/functions/exa/index.d.ts +3 -0
  78. package/dist/types/src/functions/exa/index.d.ts.map +1 -0
  79. package/dist/types/src/functions/exa/mock.d.ts +5 -0
  80. package/dist/types/src/functions/exa/mock.d.ts.map +1 -0
  81. package/dist/types/src/functions/github/fetch-prs.d.ts +6 -0
  82. package/dist/types/src/functions/github/fetch-prs.d.ts.map +1 -0
  83. package/dist/types/src/functions/index.d.ts +8 -0
  84. package/dist/types/src/functions/index.d.ts.map +1 -0
  85. package/dist/types/src/functions/linear/index.d.ts +9 -0
  86. package/dist/types/src/functions/linear/index.d.ts.map +1 -0
  87. package/dist/types/src/functions/linear/linear.test.d.ts +2 -0
  88. package/dist/types/src/functions/linear/linear.test.d.ts.map +1 -0
  89. package/dist/types/src/functions/linear/sync-issues.d.ts +12 -0
  90. package/dist/types/src/functions/linear/sync-issues.d.ts.map +1 -0
  91. package/dist/types/src/functions/research/document-create.d.ts +9 -0
  92. package/dist/types/src/functions/research/document-create.d.ts.map +1 -0
  93. package/dist/types/src/functions/research/index.d.ts +21 -0
  94. package/dist/types/src/functions/research/index.d.ts.map +1 -0
  95. package/dist/types/src/functions/research/research-graph.d.ts +18 -0
  96. package/dist/types/src/functions/research/research-graph.d.ts.map +1 -0
  97. package/dist/types/src/functions/research/research.d.ts +14 -0
  98. package/dist/types/src/functions/research/research.d.ts.map +1 -0
  99. package/dist/types/src/functions/research/research.test.d.ts +2 -0
  100. package/dist/types/src/functions/research/research.test.d.ts.map +1 -0
  101. package/dist/types/src/functions/research/types.d.ts +6 -0
  102. package/dist/types/src/functions/research/types.d.ts.map +1 -0
  103. package/dist/types/src/functions/tasks/index.d.ts +15 -0
  104. package/dist/types/src/functions/tasks/index.d.ts.map +1 -0
  105. package/dist/types/src/functions/tasks/read.d.ts +7 -0
  106. package/dist/types/src/functions/tasks/read.d.ts.map +1 -0
  107. package/dist/types/src/functions/tasks/task-list.d.ts +74 -0
  108. package/dist/types/src/functions/tasks/task-list.d.ts.map +1 -0
  109. package/dist/types/src/functions/tasks/task-list.test.d.ts +2 -0
  110. package/dist/types/src/functions/tasks/task-list.test.d.ts.map +1 -0
  111. package/dist/types/src/functions/tasks/update.d.ts +9 -0
  112. package/dist/types/src/functions/tasks/update.d.ts.map +1 -0
  113. package/dist/types/src/index.d.ts +7 -0
  114. package/dist/types/src/index.d.ts.map +1 -0
  115. package/dist/types/src/plugins.d.ts +19 -0
  116. package/dist/types/src/plugins.d.ts.map +1 -0
  117. package/dist/types/src/sync/index.d.ts +2 -0
  118. package/dist/types/src/sync/index.d.ts.map +1 -0
  119. package/dist/types/src/sync/sync.d.ts +15 -0
  120. package/dist/types/src/sync/sync.d.ts.map +1 -0
  121. package/dist/types/src/testing/data/exa-search-1748337321991.d.ts +38 -0
  122. package/dist/types/src/testing/data/exa-search-1748337321991.d.ts.map +1 -0
  123. package/dist/types/src/testing/data/exa-search-1748337331526.d.ts +37 -0
  124. package/dist/types/src/testing/data/exa-search-1748337331526.d.ts.map +1 -0
  125. package/dist/types/src/testing/data/exa-search-1748337344119.d.ts +58 -0
  126. package/dist/types/src/testing/data/exa-search-1748337344119.d.ts.map +1 -0
  127. package/dist/types/src/testing/data/index.d.ts +3 -0
  128. package/dist/types/src/testing/data/index.d.ts.map +1 -0
  129. package/dist/types/src/testing/index.d.ts +2 -0
  130. package/dist/types/src/testing/index.d.ts.map +1 -0
  131. package/dist/types/src/toolkits/AssistantToolkit.d.ts +17 -0
  132. package/dist/types/src/toolkits/AssistantToolkit.d.ts.map +1 -0
  133. package/dist/types/src/toolkits/AssistantToolkit.test.d.ts +2 -0
  134. package/dist/types/src/toolkits/AssistantToolkit.test.d.ts.map +1 -0
  135. package/dist/types/src/toolkits/SystemToolkit.d.ts +67 -0
  136. package/dist/types/src/toolkits/SystemToolkit.d.ts.map +1 -0
  137. package/dist/types/src/toolkits/index.d.ts +3 -0
  138. package/dist/types/src/toolkits/index.d.ts.map +1 -0
  139. package/dist/types/src/util/graphql.d.ts +22 -0
  140. package/dist/types/src/util/graphql.d.ts.map +1 -0
  141. package/dist/types/src/util/index.d.ts +2 -0
  142. package/dist/types/src/util/index.d.ts.map +1 -0
  143. package/dist/types/tsconfig.tsbuildinfo +1 -0
  144. package/package.json +69 -0
  145. package/src/blueprints/design/design-blueprint.test.ts +103 -0
  146. package/src/blueprints/design/design-blueprint.ts +33 -0
  147. package/src/blueprints/design/index.ts +7 -0
  148. package/src/blueprints/discord/discord-blueprint.ts +34 -0
  149. package/src/blueprints/discord/index.ts +7 -0
  150. package/src/blueprints/index.ts +10 -0
  151. package/src/blueprints/linear/index.ts +7 -0
  152. package/src/blueprints/linear/linear-blueprint.ts +35 -0
  153. package/src/blueprints/planning/index.ts +7 -0
  154. package/src/blueprints/planning/planning-blueprint.test.ts +124 -0
  155. package/src/blueprints/planning/planning-blueprint.ts +98 -0
  156. package/src/blueprints/research/index.ts +7 -0
  157. package/src/blueprints/research/research-blueprint.test.ts +7 -0
  158. package/src/blueprints/research/research-blueprint.ts +53 -0
  159. package/src/blueprints/testing.ts +34 -0
  160. package/src/blueprints/websearch/index.ts +8 -0
  161. package/src/blueprints/websearch/websearch-blueprint.ts +20 -0
  162. package/src/blueprints/websearch/websearch-toolkit.ts +8 -0
  163. package/src/crud/graph.test.ts +69 -0
  164. package/src/crud/graph.ts +388 -0
  165. package/src/crud/index.ts +5 -0
  166. package/src/experimental/feed.test.ts +111 -0
  167. package/src/functions/agent/index.ts +11 -0
  168. package/src/functions/agent/prompt.ts +114 -0
  169. package/src/functions/discord/fetch-messages.test.ts +58 -0
  170. package/src/functions/discord/fetch-messages.ts +252 -0
  171. package/src/functions/discord/index.ts +9 -0
  172. package/src/functions/document/index.ts +12 -0
  173. package/src/functions/document/read.ts +29 -0
  174. package/src/functions/document/update.ts +30 -0
  175. package/src/functions/entity-extraction/entity-extraction.conversations.json +1 -0
  176. package/src/functions/entity-extraction/entity-extraction.test.ts +92 -0
  177. package/src/functions/entity-extraction/entity-extraction.ts +164 -0
  178. package/src/functions/entity-extraction/index.ts +9 -0
  179. package/src/functions/exa/exa.ts +37 -0
  180. package/src/functions/exa/index.ts +6 -0
  181. package/src/functions/exa/mock.ts +71 -0
  182. package/src/functions/github/fetch-prs.ts +30 -0
  183. package/src/functions/index.ts +11 -0
  184. package/src/functions/linear/index.ts +9 -0
  185. package/src/functions/linear/linear.test.ts +83 -0
  186. package/src/functions/linear/sync-issues.ts +189 -0
  187. package/src/functions/research/document-create.ts +75 -0
  188. package/src/functions/research/index.ts +14 -0
  189. package/src/functions/research/research-graph.ts +47 -0
  190. package/src/functions/research/research-instructions.tpl +106 -0
  191. package/src/functions/research/research.conversations.json +1 -0
  192. package/src/functions/research/research.test.ts +179 -0
  193. package/src/functions/research/research.ts +190 -0
  194. package/src/functions/research/types.ts +26 -0
  195. package/src/functions/tasks/index.ts +11 -0
  196. package/src/functions/tasks/read.ts +34 -0
  197. package/src/functions/tasks/task-list.test.ts +99 -0
  198. package/src/functions/tasks/task-list.ts +165 -0
  199. package/src/functions/tasks/update.ts +52 -0
  200. package/src/index.ts +10 -0
  201. package/src/plugins.tsx +68 -0
  202. package/src/sync/index.ts +5 -0
  203. package/src/sync/sync.ts +87 -0
  204. package/src/testing/data/exa-search-1748337321991.ts +131 -0
  205. package/src/testing/data/exa-search-1748337331526.ts +144 -0
  206. package/src/testing/data/exa-search-1748337344119.ts +133 -0
  207. package/src/testing/data/index.ts +11 -0
  208. package/src/testing/index.ts +5 -0
  209. package/src/toolkits/AssistantToolkit.conversations.json +1 -0
  210. package/src/toolkits/AssistantToolkit.test.ts +88 -0
  211. package/src/toolkits/AssistantToolkit.ts +47 -0
  212. package/src/toolkits/SystemToolkit.ts +231 -0
  213. package/src/toolkits/index.ts +6 -0
  214. package/src/typedefs.d.ts +8 -0
  215. package/src/util/graphql.ts +31 -0
  216. package/src/util/index.ts +5 -0
@@ -0,0 +1,179 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { inspect } from 'node:util';
6
+
7
+ import { describe, it } from '@effect/vitest';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Layer from 'effect/Layer';
10
+
11
+ import { AiService, ConsolePrinter, MemoizedAiService } from '@dxos/ai';
12
+ import { TestAiService } from '@dxos/ai/testing';
13
+ import {
14
+ AiConversation,
15
+ type ContextBinding,
16
+ GenerationObserver,
17
+ makeToolExecutionServiceFromFunctions,
18
+ makeToolResolverFromFunctions,
19
+ } from '@dxos/assistant';
20
+ import { Blueprint } from '@dxos/blueprints';
21
+ import { Filter, Obj, Query, Ref } from '@dxos/echo';
22
+ import { TestHelpers, acquireReleaseResource } from '@dxos/effect';
23
+ import {
24
+ CredentialsService,
25
+ DatabaseService,
26
+ FunctionInvocationService,
27
+ QueueService,
28
+ TracingService,
29
+ } from '@dxos/functions';
30
+ import { FunctionInvocationServiceLayerTest, TestDatabaseLayer } from '@dxos/functions-runtime/testing';
31
+ import { invariant } from '@dxos/invariant';
32
+ import { ObjectId } from '@dxos/keys';
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';
36
+
37
+ import { ResearchBlueprint } from '../../blueprints';
38
+ import { testToolkit } from '../../blueprints/testing';
39
+
40
+ import { default as createDocument } from './document-create';
41
+ import { default as research } from './research';
42
+ import { ResearchGraph, queryResearchGraph } from './research-graph';
43
+ import { ResearchDataTypes } from './types';
44
+
45
+ ObjectId.dangerouslyDisableRandomness();
46
+
47
+ const TestLayer = Layer.mergeAll(
48
+ AiService.model('@anthropic/claude-opus-4-0'),
49
+ makeToolResolverFromFunctions(
50
+ [research, createDocument, MarkdownFunction.create, MarkdownFunction.open, MarkdownFunction.update],
51
+ testToolkit,
52
+ ),
53
+ makeToolExecutionServiceFromFunctions(testToolkit, testToolkit.toLayer({}) as any),
54
+ ).pipe(
55
+ Layer.provideMerge(
56
+ FunctionInvocationServiceLayerTest({
57
+ functions: [research, createDocument, MarkdownFunction.create, MarkdownFunction.open, MarkdownFunction.update],
58
+ }),
59
+ ),
60
+ Layer.provideMerge(
61
+ Layer.mergeAll(
62
+ TestAiService(),
63
+ TestDatabaseLayer({
64
+ spaceKey: 'fixed',
65
+ indexing: { vector: true },
66
+ types: [...ResearchDataTypes, ResearchGraph, Blueprint.Blueprint, Markdown.Document, HasSubject.HasSubject],
67
+ }),
68
+ CredentialsService.configuredLayer([]),
69
+ TracingService.layerNoop,
70
+ ),
71
+ ),
72
+ );
73
+
74
+ describe('Research', () => {
75
+ it.effect(
76
+ 'call a function to generate a research report',
77
+ Effect.fnUntraced(
78
+ function* (_) {
79
+ yield* DatabaseService.add(
80
+ Obj.make(Organization.Organization, {
81
+ name: 'BlueYard',
82
+ website: 'https://blueyard.com',
83
+ }),
84
+ );
85
+ yield* DatabaseService.flush({ indexes: true });
86
+ const result = yield* FunctionInvocationService.invokeFunction(research, {
87
+ query: 'Founders and portfolio of BlueYard.',
88
+ });
89
+
90
+ console.log(inspect(result, { depth: null, colors: true }));
91
+ console.log(JSON.stringify(result, null, 2));
92
+
93
+ yield* DatabaseService.flush({ indexes: true });
94
+ const researchGraph = yield* queryResearchGraph();
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
+ }
101
+ },
102
+ Effect.provide(TestLayer),
103
+ TestHelpers.provideTestContext,
104
+ ),
105
+ MemoizedAiService.isGenerationEnabled() ? 240_000 : 30_000,
106
+ );
107
+
108
+ it.scoped(
109
+ 'create and update research report',
110
+ Effect.fnUntraced(
111
+ function* (_) {
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>();
120
+ const conversation = yield* acquireReleaseResource(() => new AiConversation(queue));
121
+
122
+ yield* DatabaseService.flush({ indexes: true });
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
+ );
131
+
132
+ const observer = GenerationObserver.fromPrinter(new ConsolePrinter());
133
+
134
+ yield* conversation.createRequest({
135
+ observer,
136
+ prompt: `Create a research summary about ${organization.name}.`,
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
+ }
153
+
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
+ }
173
+ },
174
+ Effect.provide(TestLayer),
175
+ TestHelpers.provideTestContext,
176
+ ),
177
+ MemoizedAiService.isGenerationEnabled() ? 240_000 : 30_000,
178
+ );
179
+ });
@@ -0,0 +1,190 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Toolkit from '@effect/ai/Toolkit';
6
+ import * as AnthropicTool from '@effect/ai-anthropic/AnthropicTool';
7
+ import * as Array from 'effect/Array';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Function from 'effect/Function';
10
+ import * as Layer from 'effect/Layer';
11
+ import * as Option from 'effect/Option';
12
+ import * as Schema from 'effect/Schema';
13
+ import * as String from 'effect/String';
14
+
15
+ import { AiService, ConsolePrinter } from '@dxos/ai';
16
+ import {
17
+ AiSession,
18
+ GenerationObserver,
19
+ createToolkit,
20
+ makeToolExecutionServiceFromFunctions,
21
+ makeToolResolverFromFunctions,
22
+ } from '@dxos/assistant';
23
+ import { Template } from '@dxos/blueprints';
24
+ import { type DXN, Obj } from '@dxos/echo';
25
+ import { DatabaseService, TracingService, defineFunction } from '@dxos/functions';
26
+ import { FunctionInvocationServiceLayerTestMocked } from '@dxos/functions-runtime/testing';
27
+ import { type Message, Person } from '@dxos/types';
28
+ import { trim } from '@dxos/util';
29
+
30
+ import { LocalSearchHandler, LocalSearchToolkit, makeGraphWriterHandler, makeGraphWriterToolkit } from '../../crud';
31
+ import { exaFunction, exaMockFunction } from '../exa';
32
+
33
+ import { contextQueueLayerFromResearchGraph } from './research-graph';
34
+ import PROMPT from './research-instructions.tpl?raw';
35
+ import { ResearchDataTypes } from './types';
36
+
37
+ /**
38
+ * Exec external service and return the results as a Subgraph.
39
+ */
40
+ export default defineFunction({
41
+ key: 'dxos.org/function/research',
42
+ name: 'Research',
43
+ description: trim`
44
+ Search the web to research information about the given subject.
45
+ Inserts structured data into the research graph.
46
+ Creates a research summary and returns the objects created.
47
+ `,
48
+ inputSchema: Schema.Struct({
49
+ query: Schema.String.annotations({
50
+ description: trim`
51
+ The search query.
52
+ If doing research on an object then load it first and pass it as JSON.
53
+ `,
54
+ }),
55
+
56
+ instructions: Schema.optional(Schema.String).annotations({
57
+ description: trim`
58
+ The instructions for the research agent.
59
+ `,
60
+ }),
61
+
62
+ // TOOD(burdon): Move to context.
63
+ mockSearch: Schema.optional(Schema.Boolean).annotations({
64
+ description: 'Whether to use the mock search tool.',
65
+ default: false,
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
+ }),
75
+ }),
76
+ outputSchema: Schema.Struct({
77
+ document: Schema.optional(Schema.String).annotations({
78
+ description: 'The generated research document.',
79
+ }),
80
+ objects: Schema.Array(Schema.Unknown).annotations({
81
+ description: 'Structured objects created during the research process.',
82
+ }),
83
+ }),
84
+ handler: Effect.fnUntraced(
85
+ function* ({ data: { query, instructions, mockSearch = false, entityExtraction = false } }) {
86
+ if (mockSearch) {
87
+ const mockPerson = yield* DatabaseService.add(
88
+ Obj.make(Person.Person, {
89
+ preferredName: 'John Doe',
90
+ emails: [{ value: 'john.doe@example.com' }],
91
+ phoneNumbers: [{ value: '123-456-7890' }],
92
+ }),
93
+ );
94
+
95
+ return {
96
+ document: trim`
97
+ The research ran in test-mode and was mocked. Proceed as usual.
98
+ We reference John Doe to test reference: ${Obj.getDXN(mockPerson)}
99
+ `,
100
+ objects: [Obj.toJSON(mockPerson)],
101
+ };
102
+ }
103
+
104
+ yield* DatabaseService.flush({ indexes: true });
105
+ yield* TracingService.emitStatus({ message: 'Starting research...' });
106
+
107
+ const NativeWebSearch = Toolkit.make(AnthropicTool.WebSearch_20250305({}));
108
+
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));
128
+
129
+ const session = new AiSession();
130
+ const result = yield* session.run({
131
+ prompt: query,
132
+ system: join(
133
+ Template.process(PROMPT, { entityExtraction }),
134
+ instructions && `<instructions>${instructions}</instructions>`,
135
+ ),
136
+ toolkit: finishedToolkit,
137
+ observer: GenerationObserver.fromPrinter(new ConsolePrinter({ tag: 'research' })),
138
+ });
139
+
140
+ const objects = yield* Effect.forEach(objectDXNs, (dxn) => DatabaseService.resolve(dxn)).pipe(
141
+ Effect.map(Array.map((obj) => Obj.toJSON(obj))),
142
+ );
143
+
144
+ return {
145
+ document: extractLastTextBlock(result),
146
+ objects,
147
+ };
148
+ },
149
+ Effect.provide(
150
+ Layer.mergeAll(
151
+ AiService.model('@anthropic/claude-sonnet-4-0'),
152
+ // TODO(dmaretskyi): Extract.
153
+ makeToolResolverFromFunctions([exaFunction, exaMockFunction], Toolkit.make()),
154
+ makeToolExecutionServiceFromFunctions(Toolkit.make() as any, Layer.empty as any),
155
+ ).pipe(
156
+ Layer.provide(
157
+ // TODO(dmaretskyi): This should be provided by environment.
158
+ Layer.mergeAll(FunctionInvocationServiceLayerTestMocked({ functions: [exaFunction, exaMockFunction] })),
159
+ ),
160
+ ),
161
+ ),
162
+ ),
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
+ };
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
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';
8
+
9
+ /**
10
+ * Data types for research.
11
+ */
12
+ export const ResearchDataTypes: (Type.Obj.Any | Type.Relation.Any)[] = [
13
+ // Objects
14
+ Event.Event,
15
+ LegacyOrganization,
16
+ Task.Task,
17
+ Text.Text,
18
+
19
+ // Relations
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,
26
+ ];
@@ -0,0 +1,11 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { default as read$ } from './read';
6
+ import { default as update$ } from './update';
7
+
8
+ export namespace Tasks {
9
+ export const read = read$;
10
+ export const update = update$;
11
+ }
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Schema from 'effect/Schema';
7
+
8
+ import { ArtifactId } from '@dxos/assistant';
9
+ import { DatabaseService, defineFunction } from '@dxos/functions';
10
+ import { Markdown } from '@dxos/plugin-markdown/types';
11
+
12
+ export default defineFunction({
13
+ key: 'dxos.org/function/markdown/read-tasks',
14
+ name: 'Read',
15
+ description: 'Read markdown tasks.',
16
+ inputSchema: Schema.Struct({
17
+ id: ArtifactId.annotations({
18
+ description: 'The ID of the document to read.',
19
+ }),
20
+ }),
21
+ outputSchema: Schema.Struct({
22
+ content: Schema.String,
23
+ }),
24
+ handler: Effect.fn(function* ({ data: { id } }) {
25
+ const doc = yield* DatabaseService.resolve(ArtifactId.toDXN(id), Markdown.Document);
26
+
27
+ // Return content with line numbers prefixed.
28
+ const { content } = yield* DatabaseService.load(doc.content);
29
+ const lines = content.split('\n');
30
+ const len = String(lines.length).length;
31
+ const numbered = lines.map((line, i) => `${String(i + 1).padStart(len, ' ')}. ${line}`).join('\n');
32
+ return { content: numbered };
33
+ }),
34
+ });
@@ -0,0 +1,99 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import { MarkdownTasks, type TaskOperation } from './task-list';
8
+
9
+ describe('MarkdownTasks', () => {
10
+ it('should initialize with content', () => {
11
+ const content = '- [ ] Task 1\n- [x] Task 2';
12
+ const manager = new MarkdownTasks(content);
13
+ expect(manager.getRawContent()).toBe(content);
14
+ });
15
+
16
+ it('should provide numbered content', () => {
17
+ const manager = new MarkdownTasks('- [ ] Task 1\n - [ ] Subtask');
18
+ const numbered = manager.getNumberedContent();
19
+ expect(numbered).toBe(' 1→- [ ] Task 1\n 2→ - [ ] Subtask');
20
+ });
21
+
22
+ it('should insert tasks with indentation', () => {
23
+ const manager = new MarkdownTasks('- [ ] Main task');
24
+ manager.insertTask(2, 'Subtask', false, 2);
25
+ expect(manager.getRawContent()).toBe('- [ ] Main task\n - [ ] Subtask');
26
+ });
27
+
28
+ it('should insert completed tasks', () => {
29
+ const manager = new MarkdownTasks('- [ ] Task 1');
30
+ manager.insertTask(2, 'Completed task', true, 0);
31
+ expect(manager.getRawContent()).toBe('- [ ] Task 1\n- [x] Completed task');
32
+ });
33
+
34
+ it('should delete tasks', () => {
35
+ const manager = new MarkdownTasks('- [ ] Task 1\n- [ ] Task 2');
36
+ const success = manager.deleteTask(2);
37
+ expect(success).toBe(true);
38
+ expect(manager.getRawContent()).toBe('- [ ] Task 1');
39
+ });
40
+
41
+ it('should update task text', () => {
42
+ const manager = new MarkdownTasks('- [ ] Old task');
43
+ const success = manager.updateTaskText(1, 'New task description');
44
+ expect(success).toBe(true);
45
+ expect(manager.getRawContent()).toBe('- [ ] New task description');
46
+ });
47
+
48
+ it('should toggle task completion', () => {
49
+ const manager = new MarkdownTasks('- [ ] Task 1\n- [x] Task 2');
50
+
51
+ // Complete first task
52
+ manager.toggleTaskCompletion(1, true);
53
+ expect(manager.getRawContent()).toBe('- [x] Task 1\n- [x] Task 2');
54
+
55
+ // Uncomplete second task
56
+ manager.toggleTaskCompletion(2, false);
57
+ expect(manager.getRawContent()).toBe('- [x] Task 1\n- [ ] Task 2');
58
+ });
59
+
60
+ it('should change task indentation', () => {
61
+ const manager = new MarkdownTasks('- [ ] Task 1');
62
+ const success = manager.setTaskIndent(1, 4);
63
+ expect(success).toBe(true);
64
+ expect(manager.getRawContent()).toBe(' - [ ] Task 1');
65
+ });
66
+
67
+ it('should apply multiple operations', () => {
68
+ const manager = new MarkdownTasks('- [ ] Task 1');
69
+
70
+ const operations: TaskOperation[] = [
71
+ { type: 'insertTask', lineNumber: 2, text: 'New task', indent: 2 },
72
+ { type: 'toggleTaskCompletion', lineNumber: 1, completed: true },
73
+ { type: 'insertTask', lineNumber: 3, text: 'Another task', indent: 0 },
74
+ ];
75
+
76
+ manager.applyOperations(operations);
77
+ const result = manager.getRawContent();
78
+ expect(result).toBe('- [x] Task 1\n - [ ] New task\n- [ ] Another task');
79
+ });
80
+
81
+ it('should handle hierarchical task structure', () => {
82
+ const manager = new MarkdownTasks();
83
+
84
+ // Build a hierarchical structure
85
+ manager.insertTask(1, 'Main task', false, 0);
86
+ manager.insertTask(2, 'Subtask 1', false, 2);
87
+ manager.insertTask(3, 'Sub-subtask', false, 4);
88
+ manager.insertTask(4, 'Subtask 2', false, 2);
89
+
90
+ const expected = '- [ ] Main task\n - [ ] Subtask 1\n - [ ] Sub-subtask\n - [ ] Subtask 2';
91
+ expect(manager.getRawContent()).toBe(expected);
92
+ });
93
+
94
+ it('should handle line insertions at the end', () => {
95
+ const manager = new MarkdownTasks('- [ ] Task 1');
96
+ manager.insertTask(999, 'End task', false, 0);
97
+ expect(manager.getRawContent()).toBe('- [ ] Task 1\n- [ ] End task');
98
+ });
99
+ });