@dxos/assistant 0.8.3 → 0.8.4-main.3a94e84

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 (103) hide show
  1. package/dist/lib/browser/index.mjs +760 -1087
  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 +760 -1087
  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/context/binder.d.ts +34 -0
  8. package/dist/types/src/context/binder.d.ts.map +1 -0
  9. package/dist/types/src/context/binding.d.ts +56 -0
  10. package/dist/types/src/context/binding.d.ts.map +1 -0
  11. package/dist/types/src/context/index.d.ts +3 -0
  12. package/dist/types/src/context/index.d.ts.map +1 -0
  13. package/dist/types/src/conversation/conversation.d.ts +38 -0
  14. package/dist/types/src/conversation/conversation.d.ts.map +1 -0
  15. package/dist/types/src/conversation/index.d.ts +2 -0
  16. package/dist/types/src/conversation/index.d.ts.map +1 -0
  17. package/dist/types/src/errors.d.ts +38 -0
  18. package/dist/types/src/errors.d.ts.map +1 -0
  19. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
  20. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +135 -41
  21. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
  22. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts +1 -1
  23. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
  24. package/dist/types/src/extraction/extraction.d.ts +100 -42
  25. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  26. package/dist/types/src/functions/index.d.ts +2 -0
  27. package/dist/types/src/functions/index.d.ts.map +1 -0
  28. package/dist/types/src/functions/services.d.ts +6 -0
  29. package/dist/types/src/functions/services.d.ts.map +1 -0
  30. package/dist/types/src/index.d.ts +4 -1
  31. package/dist/types/src/index.d.ts.map +1 -1
  32. package/dist/types/src/research/exa.d.ts +10 -5
  33. package/dist/types/src/research/exa.d.ts.map +1 -1
  34. package/dist/types/src/research/graph.d.ts +18 -8
  35. package/dist/types/src/research/graph.d.ts.map +1 -1
  36. package/dist/types/src/research/research.d.ts +1 -1
  37. package/dist/types/src/research/research.d.ts.map +1 -1
  38. package/dist/types/src/session/session.d.ts +183 -184
  39. package/dist/types/src/session/session.d.ts.map +1 -1
  40. package/dist/types/src/session/version-pin.d.ts +2 -2
  41. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  42. package/dist/types/src/templates/index.d.ts +2 -0
  43. package/dist/types/src/templates/index.d.ts.map +1 -0
  44. package/dist/types/src/templates/prompt.d.ts +30 -0
  45. package/dist/types/src/templates/prompt.d.ts.map +1 -0
  46. package/dist/types/src/types.d.ts +10 -0
  47. package/dist/types/src/types.d.ts.map +1 -1
  48. package/dist/types/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +29 -30
  50. package/src/context/binder.ts +103 -0
  51. package/src/context/binding.ts +32 -0
  52. package/src/context/index.ts +6 -0
  53. package/src/conversation/conversation.ts +96 -0
  54. package/src/conversation/index.ts +5 -0
  55. package/src/errors.ts +10 -0
  56. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +6 -18
  57. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +19 -14
  58. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +6 -3
  59. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +2 -2
  60. package/src/extraction/extraction-ner-function/named-entity-recognition.ts +1 -1
  61. package/src/functions/index.ts +5 -0
  62. package/src/functions/services.ts +139 -0
  63. package/src/index.ts +4 -1
  64. package/src/research/exa.ts +56 -59
  65. package/src/research/graph.test.ts +50 -1
  66. package/src/research/graph.ts +85 -53
  67. package/src/research/instructions-research.tpl +1 -0
  68. package/src/research/research.test.ts +27 -27
  69. package/src/research/research.ts +50 -52
  70. package/src/session/__snapshots__/session.test.ts.snap +32 -0
  71. package/src/session/session.test.ts +99 -18
  72. package/src/session/session.ts +230 -443
  73. package/src/session/version-pin.test.ts +2 -21
  74. package/src/session/version-pin.ts +5 -5
  75. package/src/summarization/summarization.test.ts +75 -88
  76. package/src/templates/assistant.tpl +111 -0
  77. package/src/templates/index.ts +5 -0
  78. package/src/templates/prompt.ts +45 -0
  79. package/src/types.ts +40 -1
  80. package/dist/lib/node/index.cjs +0 -2178
  81. package/dist/lib/node/index.cjs.map +0 -7
  82. package/dist/lib/node/meta.json +0 -1
  83. package/dist/types/src/blueprint/ai.test.d.ts +0 -2
  84. package/dist/types/src/blueprint/ai.test.d.ts.map +0 -1
  85. package/dist/types/src/blueprint/blueprint.d.ts +0 -55
  86. package/dist/types/src/blueprint/blueprint.d.ts.map +0 -1
  87. package/dist/types/src/blueprint/blueprint.test.d.ts +0 -2
  88. package/dist/types/src/blueprint/blueprint.test.d.ts.map +0 -1
  89. package/dist/types/src/blueprint/index.d.ts +0 -4
  90. package/dist/types/src/blueprint/index.d.ts.map +0 -1
  91. package/dist/types/src/blueprint/logger.d.ts +0 -25
  92. package/dist/types/src/blueprint/logger.d.ts.map +0 -1
  93. package/dist/types/src/blueprint/machine.d.ts +0 -143
  94. package/dist/types/src/blueprint/machine.d.ts.map +0 -1
  95. package/dist/types/src/blueprint/test-data.d.ts +0 -3
  96. package/dist/types/src/blueprint/test-data.d.ts.map +0 -1
  97. package/src/blueprint/ai.test.ts +0 -156
  98. package/src/blueprint/blueprint.test.ts +0 -151
  99. package/src/blueprint/blueprint.ts +0 -75
  100. package/src/blueprint/index.ts +0 -7
  101. package/src/blueprint/logger.ts +0 -155
  102. package/src/blueprint/machine.ts +0 -251
  103. package/src/blueprint/test-data.ts +0 -55
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/assistant",
3
- "version": "0.8.3",
3
+ "version": "0.8.4-main.3a94e84",
4
4
  "description": "Natural language interface to DXOS",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -21,41 +21,40 @@
21
21
  "src"
22
22
  ],
23
23
  "dependencies": {
24
+ "@effect/ai": "0.23.0",
25
+ "@effect/ai-anthropic": "^0.13.0",
26
+ "@effect/platform": "^0.89.0",
27
+ "@effect/platform-node": "^0.92.0",
24
28
  "@preact/signals-core": "^1.9.0",
25
29
  "@xenova/transformers": "^2.17.2",
26
30
  "chalk": "^4.1.0",
27
31
  "exa-js": "^1.5.12",
28
- "@dxos/ai": "0.8.3",
29
- "@dxos/artifact": "0.8.3",
30
- "@dxos/context": "0.8.3",
31
- "@dxos/async": "0.8.3",
32
- "@dxos/debug": "0.8.3",
33
- "@dxos/echo": "0.8.3",
34
- "@dxos/echo-db": "0.8.3",
35
- "@dxos/echo-protocol": "0.8.3",
36
- "@dxos/echo-schema": "0.8.3",
37
- "@dxos/functions": "0.8.3",
38
- "@dxos/graph": "0.8.3",
39
- "@dxos/effect": "0.8.3",
40
- "@dxos/keys": "0.8.3",
41
- "@dxos/invariant": "0.8.3",
42
- "@dxos/live-object": "0.8.3",
43
- "@dxos/log": "0.8.3",
44
- "@dxos/node-std": "0.8.3",
45
- "@dxos/util": "0.8.3",
46
- "@dxos/schema": "0.8.3"
32
+ "handlebars": "4.7.8",
33
+ "lodash.defaultsdeep": "^4.6.1",
34
+ "@dxos/ai": "0.8.4-main.3a94e84",
35
+ "@dxos/blueprints": "0.8.4-main.3a94e84",
36
+ "@dxos/async": "0.8.4-main.3a94e84",
37
+ "@dxos/context": "0.8.4-main.3a94e84",
38
+ "@dxos/debug": "0.8.4-main.3a94e84",
39
+ "@dxos/echo": "0.8.4-main.3a94e84",
40
+ "@dxos/echo-schema": "0.8.4-main.3a94e84",
41
+ "@dxos/effect": "0.8.4-main.3a94e84",
42
+ "@dxos/echo-db": "0.8.4-main.3a94e84",
43
+ "@dxos/echo-protocol": "0.8.4-main.3a94e84",
44
+ "@dxos/errors": "0.8.4-main.3a94e84",
45
+ "@dxos/functions": "0.8.4-main.3a94e84",
46
+ "@dxos/graph": "0.8.4-main.3a94e84",
47
+ "@dxos/invariant": "0.8.4-main.3a94e84",
48
+ "@dxos/keys": "0.8.4-main.3a94e84",
49
+ "@dxos/live-object": "0.8.4-main.3a94e84",
50
+ "@dxos/log": "0.8.4-main.3a94e84",
51
+ "@dxos/schema": "0.8.4-main.3a94e84",
52
+ "@dxos/node-std": "0.8.4-main.3a94e84",
53
+ "@dxos/util": "0.8.4-main.3a94e84"
47
54
  },
48
55
  "devDependencies": {
49
- "@effect/ai": "^0.19.4",
50
- "@effect/ai-anthropic": "^0.9.5",
51
- "@effect/ai-openai": "^0.22.4",
52
- "@effect/cluster": "^0.39.4",
53
- "@effect/experimental": "^0.49.2",
54
- "@effect/platform": "^0.85.2",
55
- "@effect/platform-node": "^0.86.4",
56
- "@effect/rpc": "^0.62.4",
57
- "@effect/sql": "^0.38.2",
58
- "effect": "3.14.21",
56
+ "@types/lodash.defaultsdeep": "^4.6.6",
57
+ "effect": "3.17.0",
59
58
  "inquirer": "^12.0.0"
60
59
  },
61
60
  "peerDependencies": {
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { computed, type ReadonlySignal } from '@preact/signals-core';
6
+ import { Array, pipe } from 'effect';
7
+
8
+ import { type Blueprint } from '@dxos/blueprints';
9
+ import { Obj, type Type, type Ref, type Relation } from '@dxos/echo';
10
+ import { type Queue } from '@dxos/echo-db';
11
+ import { ComplexSet } from '@dxos/util';
12
+
13
+ import { ContextBinding } from './binding';
14
+
15
+ /**
16
+ * Manages bindings of blueprints and objects to a conversation.
17
+ */
18
+ export class ContextBinder {
19
+ constructor(private readonly _queue: Queue) {}
20
+
21
+ /**
22
+ * Reactive query of all bindings.
23
+ */
24
+ // TODO(burdon): Cache value?
25
+ readonly bindings: ReadonlySignal<Bindings> = computed(() => this._reduce(this._queue.objects));
26
+
27
+ // TODO(burdon): load refs?
28
+ readonly blueprints: ReadonlySignal<Ref.Ref<Blueprint.Blueprint>[]> = computed(() => [
29
+ ...this.bindings.value.blueprints,
30
+ ]);
31
+ readonly objects: ReadonlySignal<Ref.Ref<Type.Expando>[]> = computed(() => [...this.bindings.value.objects]);
32
+
33
+ /**
34
+ * Asynchronous query of all bindings.
35
+ */
36
+ async query(): Promise<Bindings> {
37
+ const queueItems = await this._queue.queryObjects();
38
+ return this._reduce(queueItems);
39
+ }
40
+
41
+ // TODO(burdon): Pass in Blueprint obj (from registry?) and create reference.
42
+ async bind(props: BindingProps): Promise<void> {
43
+ if (!props.blueprints?.length && !props.objects?.length) {
44
+ return;
45
+ }
46
+
47
+ await this._queue.append([
48
+ Obj.make(ContextBinding, {
49
+ blueprints: {
50
+ added: props.blueprints ?? [],
51
+ removed: [],
52
+ },
53
+ objects: {
54
+ added: props.objects ?? [],
55
+ removed: [],
56
+ },
57
+ }),
58
+ ]);
59
+ }
60
+
61
+ async unbind(props: BindingProps): Promise<void> {
62
+ if (!props.blueprints?.length && !props.objects?.length) {
63
+ return;
64
+ }
65
+
66
+ await this._queue.append([
67
+ Obj.make(ContextBinding, {
68
+ blueprints: {
69
+ added: [],
70
+ removed: props.blueprints ?? [],
71
+ },
72
+ objects: {
73
+ added: [],
74
+ removed: props.objects ?? [],
75
+ },
76
+ }),
77
+ ]);
78
+ }
79
+
80
+ private _reduce(items: (Obj.Any | Relation.Any)[]): Bindings {
81
+ return pipe(
82
+ items,
83
+ Array.filter(Obj.instanceOf(ContextBinding)),
84
+ Array.reduce(new Bindings(), (context, item) => {
85
+ item.blueprints.removed.forEach((item) => context.blueprints.delete(item));
86
+ item.blueprints.added.forEach((item) => context.blueprints.add(item));
87
+ item.objects.removed.forEach((item) => context.objects.delete(item));
88
+ item.objects.added.forEach((item) => context.objects.add(item));
89
+ return context;
90
+ }),
91
+ );
92
+ }
93
+ }
94
+
95
+ export type BindingProps = Partial<{
96
+ blueprints: Ref.Ref<Blueprint.Blueprint>[];
97
+ objects: Ref.Ref<Type.Expando>[];
98
+ }>;
99
+
100
+ export class Bindings {
101
+ blueprints = new ComplexSet<Ref.Ref<Blueprint.Blueprint>>((ref) => ref.dxn.toString());
102
+ objects = new ComplexSet<Ref.Ref<Type.Expando>>((ref) => ref.dxn.toString());
103
+ }
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Schema } from 'effect';
6
+
7
+ import { Blueprint } from '@dxos/blueprints';
8
+ import { Type } from '@dxos/echo';
9
+
10
+ /**
11
+ * Thread message that binds or unbinds contextual objects to a conversation.
12
+ */
13
+ // TODO(burdon): Make ContentBlock.
14
+ export const ContextBinding = Schema.Struct({
15
+ blueprints: Schema.Struct({
16
+ added: Schema.Array(Type.Ref(Blueprint.Blueprint)),
17
+ removed: Schema.Array(Type.Ref(Blueprint.Blueprint)),
18
+ }),
19
+
20
+ // TODO(burdon): Type.Expando => Type.Obj (or Obj.Any?)
21
+ objects: Schema.Struct({
22
+ added: Schema.Array(Type.Ref(Type.Expando)),
23
+ removed: Schema.Array(Type.Ref(Type.Expando)),
24
+ }),
25
+ }).pipe(
26
+ Type.Obj({
27
+ typename: 'dxos.org/type/ContextBinding',
28
+ version: '0.1.0',
29
+ }),
30
+ );
31
+
32
+ export interface ContextBinding extends Schema.Schema.Type<typeof ContextBinding> {}
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './binder';
6
+ export * from './binding';
@@ -0,0 +1,96 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type AiError, type AiLanguageModel, type AiTool, type AiToolkit } from '@effect/ai';
6
+ import { Effect } from 'effect';
7
+
8
+ import {
9
+ type ToolExecutionService,
10
+ type ToolResolverService,
11
+ type AiInputPreprocessingError,
12
+ type AiToolNotFoundError,
13
+ } from '@dxos/ai';
14
+ import { Event } from '@dxos/async';
15
+ import { Obj } from '@dxos/echo';
16
+ import { type Queue } from '@dxos/echo-db';
17
+ import { DatabaseService } from '@dxos/functions';
18
+ import { DataType } from '@dxos/schema';
19
+
20
+ import { ContextBinder, type ContextBinding } from '../context';
21
+ import type { AiAssistantError } from '../errors';
22
+ import { AiSession } from '../session';
23
+
24
+ export interface ConversationRunOptions<Tools extends AiTool.Any> {
25
+ systemPrompt?: string;
26
+ prompt: string;
27
+
28
+ toolkit?: AiToolkit.AiToolkit<Tools>;
29
+ }
30
+
31
+ export type ConversationOptions = {
32
+ queue: Queue<DataType.Message | ContextBinding>;
33
+ };
34
+
35
+ /**
36
+ * Persistent conversation state.
37
+ * Context + history + artifacts.
38
+ * Backed by a Queue.
39
+ */
40
+ export class Conversation {
41
+ private readonly _queue: Queue<DataType.Message | ContextBinding>;
42
+
43
+ /**
44
+ * Fired when the execution loop begins.
45
+ * This is called before the first message is sent.
46
+ */
47
+ public readonly onBegin = new Event<AiSession>();
48
+
49
+ /**
50
+ * Blueprints bound to the conversation.
51
+ */
52
+ public readonly context: ContextBinder;
53
+
54
+ constructor(options: ConversationOptions) {
55
+ this._queue = options.queue;
56
+ this.context = new ContextBinder(this._queue);
57
+ }
58
+
59
+ async getHistory(): Promise<DataType.Message[]> {
60
+ const queueItems = await this._queue.queryObjects();
61
+ return queueItems.filter(Obj.instanceOf(DataType.Message));
62
+ }
63
+
64
+ run = <Tools extends AiTool.Any>(
65
+ options: ConversationRunOptions<Tools>,
66
+ ): Effect.Effect<
67
+ DataType.Message[],
68
+ AiAssistantError | AiInputPreprocessingError | AiError.AiError | AiToolNotFoundError,
69
+ AiLanguageModel.AiLanguageModel | ToolResolverService | ToolExecutionService | AiTool.ToHandler<Tools>
70
+ > =>
71
+ Effect.gen(this, function* () {
72
+ const session = new AiSession();
73
+ this.onBegin.emit(session);
74
+
75
+ const history = yield* Effect.promise(() => this.getHistory());
76
+ const context = yield* Effect.promise(() => this.context.query());
77
+ const blueprints = yield* Effect.forEach(context.blueprints.values(), DatabaseService.loadRef);
78
+
79
+ const contextObjects = yield* Effect.forEach(context.objects.values(), DatabaseService.loadRef);
80
+
81
+ const systemPrompt =
82
+ (options.systemPrompt ?? '') +
83
+ '\n\n' +
84
+ contextObjects.map((obj) => `<object>${Obj.getDXN(obj)}</object>`).join('\n');
85
+
86
+ const messages = yield* session.run({
87
+ prompt: options.prompt,
88
+ history,
89
+ systemPrompt,
90
+ toolkit: options.toolkit,
91
+ blueprints,
92
+ });
93
+ yield* Effect.promise(() => this._queue.append(messages));
94
+ return messages;
95
+ }).pipe(Effect.withSpan('Conversation.run'));
96
+ }
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './conversation';
package/src/errors.ts ADDED
@@ -0,0 +1,10 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { BaseError } from '@dxos/errors';
6
+
7
+ /**
8
+ * Generic error for AI agent execution.
9
+ */
10
+ export class AiAssistantError extends BaseError.extend('AI_ASSISTANT_ERROR') {}
@@ -2,35 +2,23 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { describe, test, beforeAll } from 'vitest';
5
+ import { beforeAll, describe, test } from 'vitest';
6
6
 
7
- import { EdgeAiServiceClient } from '@dxos/ai';
8
- import { AI_SERVICE_ENDPOINT } from '@dxos/ai/testing';
9
- import { FunctionExecutor, ServiceContainer } from '@dxos/functions';
7
+ import { todo } from '@dxos/debug';
8
+ import { FunctionExecutor } from '@dxos/functions';
10
9
  import { log } from '@dxos/log';
11
10
  import { createTestData } from '@dxos/schema/testing';
12
11
  import { range } from '@dxos/util';
13
12
 
14
- import { extractionAnthropicFn } from './extraction-llm-function';
15
13
  import { processTranscriptMessage } from '../extraction';
14
+ import { extractionAnthropicFn } from './extraction-llm-function';
16
15
 
16
+ // TODO(burdon): Rewrite test.
17
17
  describe.skip('LLM EntityExtraction', () => {
18
18
  let executor: FunctionExecutor;
19
19
 
20
20
  beforeAll(async () => {
21
- executor = new FunctionExecutor(
22
- new ServiceContainer().setServices({
23
- ai: {
24
- client: new EdgeAiServiceClient({
25
- endpoint: AI_SERVICE_ENDPOINT.REMOTE,
26
- defaultGenerationOptions: {
27
- // model: '@anthropic/claude-sonnet-4-20250514',
28
- model: '@anthropic/claude-3-5-haiku-20241022',
29
- },
30
- }),
31
- },
32
- }),
33
- );
21
+ executor = new FunctionExecutor(todo());
34
22
  });
35
23
 
36
24
  test('should process a transcript block', async () => {
@@ -2,14 +2,17 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Message } from '@dxos/ai';
5
+ // TODO(burdon): Fix.
6
+ // @ts-nocheck
7
+
8
+ import { ToolRegistry, AiService } from '@dxos/ai';
6
9
  import { Obj } from '@dxos/echo';
7
10
  import { create } from '@dxos/echo-schema';
8
- import { AiService, defineFunction, type FunctionDefinition } from '@dxos/functions';
9
- import { DataType } from '@dxos/schema';
11
+ import { defineFunction, type FunctionDefinition } from '@dxos/functions';
12
+ import { type ContentBlock, DataType } from '@dxos/schema';
10
13
 
11
14
  import PROMPT from './instructions.tpl?raw';
12
- import { AISession } from '../../session';
15
+ import { AiSession } from '../../session';
13
16
  import { ExtractionInput, ExtractionOutput } from '../extraction';
14
17
  import { insertReferences, ReferencedQuotes } from '../quotes';
15
18
 
@@ -20,7 +23,7 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
20
23
  handler: async ({ data: { message, objects }, context }) => {
21
24
  const startTime = performance.now();
22
25
  const ai = context.getService(AiService);
23
- const session = new AISession({ operationModel: 'configured' });
26
+ const session = new AiSession({ operationModel: 'configured' });
24
27
  const result = await session.runStructured(ReferencedQuotes, {
25
28
  generationOptions: {
26
29
  model: '@anthropic/claude-3-5-haiku-20241022',
@@ -28,30 +31,32 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
28
31
  client: ai.client,
29
32
  systemPrompt: PROMPT,
30
33
  history: [
31
- Obj.make(Message, {
32
- role: 'user',
33
- content: [
34
+ Obj.make(DataType.Message, {
35
+ created: new Date().toISOString(),
36
+ sender: { role: 'user' },
37
+ blocks: [
34
38
  {
35
- type: 'text',
39
+ _tag: 'text',
36
40
  text: `<context>${JSON.stringify(objects)}</context>`,
37
- },
41
+ } satisfies ContentBlock.Text,
38
42
  {
39
- type: 'text',
43
+ _tag: 'text',
40
44
  text: `<transcript>${JSON.stringify(message.blocks)}</transcript>`,
41
- },
45
+ } satisfies ContentBlock.Text,
42
46
  ],
43
47
  }),
44
48
  ],
45
49
  artifacts: [],
46
50
  prompt: '',
47
51
  tools: [],
48
- });
52
+ toolResolver: new ToolRegistry([]),
53
+ } as any); // TODO(burdon): Rewrite test.
49
54
 
50
55
  return {
51
56
  message: create(DataType.Message, {
52
57
  ...message,
53
58
  blocks: message.blocks.map((block, i) =>
54
- block.type !== 'transcription'
59
+ block._tag !== 'transcript'
55
60
  ? block
56
61
  : {
57
62
  ...block,
@@ -2,6 +2,9 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ // TODO(burdon): Fix.
6
+ // @ts-nocheck
7
+
5
8
  import { asyncTimeout } from '@dxos/async';
6
9
  import { create } from '@dxos/echo-schema';
7
10
  import { DatabaseService, defineFunction } from '@dxos/functions';
@@ -25,7 +28,7 @@ export const extractionNerFn = defineFunction({
25
28
 
26
29
  const entitiesPromise = Promise.all(
27
30
  message.blocks.map(async (block) => {
28
- invariant(block.type === 'transcription' || block.type === 'text', 'Block must have text');
31
+ invariant(block._tag === 'transcript' || block._tag === 'text', 'Block must have text');
29
32
  return extractFullEntities(block.text);
30
33
  }),
31
34
  ).then((entities) => entities.flat());
@@ -36,8 +39,8 @@ export const extractionNerFn = defineFunction({
36
39
  db,
37
40
  );
38
41
  const blocksWithReferences = message.blocks.map((block, i) => {
39
- invariant(block.type === 'transcription' || block.type === 'text', 'Block must have text');
40
- return { ...block, text: insertReferences(block.text, quoteReferences) };
42
+ invariant(block._tag === 'transcript' || block._tag === 'text', 'Block must have text');
43
+ return { ...block, text: insertReferences(block._tag, quoteReferences) };
41
44
  });
42
45
  const messageWithReferences = create(DataType.Message, {
43
46
  ...message,
@@ -57,7 +57,7 @@ describe('NamedEntityRecognition', () => {
57
57
  const { transcriptWoflram, transcriptJosiah } = await createTestData();
58
58
 
59
59
  const blocks = [...transcriptWoflram, ...transcriptJosiah].flatMap((message) =>
60
- message.blocks.flatMap((block) => (block.type === 'transcription' ? [block.text] : [])),
60
+ message.blocks.flatMap((block) => (block._tag === 'transcript' ? [block.text] : [])),
61
61
  );
62
62
 
63
63
  for (const block of blocks) {
@@ -70,7 +70,7 @@ describe('NamedEntityRecognition', () => {
70
70
  const { transcriptMessages } = await createTestData();
71
71
 
72
72
  const blocks = transcriptMessages.flatMap((message) =>
73
- message.blocks.flatMap((block) => (block.type === 'transcription' ? [block.text] : [])),
73
+ message.blocks.flatMap((block) => (block._tag === 'transcript' ? [block.text] : [])),
74
74
  );
75
75
 
76
76
  for (const block of blocks) {
@@ -4,9 +4,9 @@
4
4
 
5
5
  import {
6
6
  type TokenClassificationOutput,
7
+ type TokenClassificationPipelineType,
7
8
  type TokenClassificationSingle,
8
9
  pipeline,
9
- type TokenClassificationPipelineType,
10
10
  } from '@xenova/transformers';
11
11
 
12
12
  import { log } from '@dxos/log';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './services';
@@ -0,0 +1,139 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { AiTool } from '@effect/ai';
6
+ import { Context, Effect, Layer, Match, Predicate, Record, Schema } from 'effect';
7
+
8
+ import { AiToolNotFoundError, ToolExecutionService, ToolResolverService } from '@dxos/ai';
9
+ import { todo } from '@dxos/debug';
10
+ import { FunctionError, type FunctionContext, type FunctionDefinition, type Services } from '@dxos/functions';
11
+ import { invariant } from '@dxos/invariant';
12
+
13
+ export const makeToolResolverFromFunctions = (
14
+ functions: FunctionDefinition<any, any>[],
15
+ ): Layer.Layer<ToolResolverService> => {
16
+ return Layer.succeed(ToolResolverService, {
17
+ resolve: Effect.fn('resolveTool')(function* (id) {
18
+ const fn = functions.find((f) => f.name === id);
19
+ if (!fn) {
20
+ return yield* Effect.fail(new AiToolNotFoundError(id));
21
+ }
22
+
23
+ return projectFunctionToTool(fn);
24
+ }),
25
+ });
26
+ };
27
+
28
+ export const makeToolExecutionServiceFromFunctions = (
29
+ functions: FunctionDefinition<any, any>[],
30
+ ): Layer.Layer<ToolExecutionService> => {
31
+ return Layer.succeed(ToolExecutionService, {
32
+ handlersFor: (toolkit) => {
33
+ const makeHandler = (tool: AiTool.Any): ((params: unknown) => Effect.Effect<unknown, any, any>) => {
34
+ return Effect.fn('toolFunctionHandler')(function* (input: any) {
35
+ const { functionName } = Context.get(FunctionToolAnnotation)(tool.annotations as any);
36
+ const fnDef = functions.find((f) => f.name === functionName);
37
+ if (!fnDef) {
38
+ return yield* Effect.fail(new AiToolNotFoundError(tool.name));
39
+ }
40
+
41
+ return yield* invokeFunction(fnDef, input).pipe(Effect.catchAllDefect((defect) => Effect.fail(defect)));
42
+ });
43
+ };
44
+
45
+ return toolkit.of(Record.map(toolkit.tools, (tool, name) => makeHandler(tool)) as any) as any;
46
+ },
47
+ });
48
+ };
49
+
50
+ /**
51
+ * Tools that are projected from functions have this annotation.
52
+ */
53
+ class FunctionToolAnnotation extends Context.Tag('@dxos/assisatnt/FunctionToolAnnotation')<
54
+ FunctionToolAnnotation,
55
+ {
56
+ functionName: string;
57
+ }
58
+ >() {}
59
+
60
+ const toolCache = new WeakMap<FunctionDefinition<any, any>, AiTool.Any>();
61
+ const projectFunctionToTool = (fn: FunctionDefinition<any, any>): AiTool.Any => {
62
+ if (toolCache.has(fn)) {
63
+ return toolCache.get(fn)!;
64
+ }
65
+ const tool = AiTool.make(makeToolName(fn.name), {
66
+ description: fn.description,
67
+ parameters: createStructFieldsFromSchema(fn.inputSchema),
68
+ // TODO(dmaretskyi): Include output schema.
69
+ failure: Schema.Any, // TODO(dmaretskyi): Better type for the failure?
70
+ }).annotate(FunctionToolAnnotation, { functionName: fn.name });
71
+ toolCache.set(fn, tool);
72
+ return tool;
73
+ };
74
+
75
+ /**
76
+ * @returns Tool name produced from function name by escaping invalid characters.
77
+ */
78
+ const makeToolName = (name: string) => {
79
+ const toolName = name.replace(/[^a-zA-Z0-9]/g, '');
80
+ invariant(toolName.match(/^[a-zA-Z_][a-zA-Z0-9-_]*$/));
81
+ return toolName;
82
+ };
83
+
84
+ // TODO(dmaretskyi): Factor out.
85
+ const createStructFieldsFromSchema = (schema: Schema.Schema<any, any>): Record<string, Schema.Schema<any, any>> => {
86
+ return Match.value(schema.ast).pipe(
87
+ Match.when(Predicate.isTagged('TypeLiteral'), (ast) => {
88
+ return Object.fromEntries(ast.propertySignatures.map((prop) => [prop.name, Schema.make(prop.type)]));
89
+ }),
90
+ Match.orElseAbsurd,
91
+ );
92
+ };
93
+
94
+ // TODO(dmaretskyi): Factor out.
95
+ const invokeFunction = (fnDef: FunctionDefinition<any, any>, input: any): Effect.Effect<unknown, never, Services> =>
96
+ Effect.gen(function* () {
97
+ // Assert input matches schema
98
+ const assertInput = fnDef.inputSchema.pipe(Schema.asserts);
99
+ (assertInput as any)(input);
100
+
101
+ const context: FunctionContext = {
102
+ getService: () => todo(),
103
+ getSpace: async (_spaceId: any) => {
104
+ throw new Error('Not available. Use the database service instead.');
105
+ },
106
+ space: undefined,
107
+ get ai(): never {
108
+ throw new Error('Not available. Use the ai service instead.');
109
+ },
110
+ };
111
+
112
+ // TODO(dmaretskyi): This should be delegated to a function invoker service.
113
+ const data = yield* Effect.gen(function* () {
114
+ const result = fnDef.handler({ context, data: input });
115
+ if (Effect.isEffect(result)) {
116
+ return yield* (result as Effect.Effect<unknown, unknown, Services>).pipe(Effect.orDie);
117
+ } else if (
118
+ typeof result === 'object' &&
119
+ result !== null &&
120
+ 'then' in result &&
121
+ typeof result.then === 'function'
122
+ ) {
123
+ return yield* Effect.promise(() => result);
124
+ } else {
125
+ return result;
126
+ }
127
+ }).pipe(
128
+ Effect.orDie,
129
+ Effect.catchAllDefect((defect) =>
130
+ Effect.die(new FunctionError('Error running function', { context: { name: fnDef.name }, cause: defect })),
131
+ ),
132
+ );
133
+
134
+ // Assert output matches schema
135
+ const assertOutput = fnDef.outputSchema?.pipe(Schema.asserts);
136
+ (assertOutput as any)(data);
137
+
138
+ return data;
139
+ }).pipe(Effect.withSpan('invokeFunction', { attributes: { name: fnDef.name } }));
package/src/index.ts CHANGED
@@ -2,8 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
+ export * from './context';
6
+ export * from './conversation';
5
7
  export * from './extraction';
8
+ export * from './functions';
6
9
  export * from './research';
7
10
  export * from './session';
11
+ export * from './templates';
8
12
  export * from './types';
9
- export * from './blueprint';