@dxos/assistant 0.8.4-main.84f28bd → 0.8.4-main.b97322e

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 (82) hide show
  1. package/dist/lib/browser/index.mjs +623 -811
  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 +623 -811
  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 +4 -4
  8. package/dist/types/src/context/binder.d.ts.map +1 -1
  9. package/dist/types/src/context/binding.d.ts +24 -6
  10. package/dist/types/src/context/binding.d.ts.map +1 -1
  11. package/dist/types/src/conversation/conversation.d.ts +12 -18
  12. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  13. package/dist/types/src/errors.d.ts +38 -0
  14. package/dist/types/src/errors.d.ts.map +1 -0
  15. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
  16. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +127 -33
  17. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
  18. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts +1 -1
  19. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
  20. package/dist/types/src/extraction/extraction.d.ts +100 -42
  21. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  22. package/dist/types/src/functions/index.d.ts +2 -0
  23. package/dist/types/src/functions/index.d.ts.map +1 -0
  24. package/dist/types/src/functions/services.d.ts +6 -0
  25. package/dist/types/src/functions/services.d.ts.map +1 -0
  26. package/dist/types/src/index.d.ts +2 -2
  27. package/dist/types/src/index.d.ts.map +1 -1
  28. package/dist/types/src/research/exa.d.ts +10 -5
  29. package/dist/types/src/research/exa.d.ts.map +1 -1
  30. package/dist/types/src/research/graph.d.ts +18 -8
  31. package/dist/types/src/research/graph.d.ts.map +1 -1
  32. package/dist/types/src/research/research.d.ts +1 -1
  33. package/dist/types/src/research/research.d.ts.map +1 -1
  34. package/dist/types/src/session/session.d.ts +170 -193
  35. package/dist/types/src/session/session.d.ts.map +1 -1
  36. package/dist/types/src/session/version-pin.d.ts +2 -2
  37. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  38. package/dist/types/src/templates/index.d.ts +2 -0
  39. package/dist/types/src/templates/index.d.ts.map +1 -0
  40. package/dist/types/src/templates/prompt.d.ts +30 -0
  41. package/dist/types/src/templates/prompt.d.ts.map +1 -0
  42. package/dist/types/src/types.d.ts +10 -0
  43. package/dist/types/src/types.d.ts.map +1 -1
  44. package/dist/types/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +29 -21
  46. package/src/context/binder.ts +6 -4
  47. package/src/context/binding.ts +4 -3
  48. package/src/conversation/conversation.ts +54 -61
  49. package/src/errors.ts +10 -0
  50. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +6 -18
  51. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +18 -14
  52. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +6 -3
  53. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +2 -2
  54. package/src/extraction/extraction-ner-function/named-entity-recognition.ts +1 -1
  55. package/src/functions/index.ts +5 -0
  56. package/src/functions/services.ts +139 -0
  57. package/src/index.ts +2 -2
  58. package/src/research/exa.ts +56 -59
  59. package/src/research/graph.test.ts +50 -1
  60. package/src/research/graph.ts +85 -53
  61. package/src/research/instructions-research.tpl +1 -0
  62. package/src/research/research.test.ts +27 -27
  63. package/src/research/research.ts +50 -53
  64. package/src/session/__snapshots__/session.test.ts.snap +32 -0
  65. package/src/session/session.test.ts +90 -55
  66. package/src/session/session.ts +215 -461
  67. package/src/session/version-pin.test.ts +2 -21
  68. package/src/session/version-pin.ts +5 -5
  69. package/src/summarization/summarization.test.ts +75 -88
  70. package/src/templates/assistant.tpl +111 -0
  71. package/src/templates/index.ts +5 -0
  72. package/src/templates/prompt.ts +45 -0
  73. package/src/types.ts +40 -1
  74. package/dist/types/src/blueprint/blueprint.d.ts +0 -41
  75. package/dist/types/src/blueprint/blueprint.d.ts.map +0 -1
  76. package/dist/types/src/blueprint/index.d.ts +0 -3
  77. package/dist/types/src/blueprint/index.d.ts.map +0 -1
  78. package/dist/types/src/blueprint/registry.d.ts +0 -7
  79. package/dist/types/src/blueprint/registry.d.ts.map +0 -1
  80. package/src/blueprint/blueprint.ts +0 -75
  81. package/src/blueprint/index.ts +0 -6
  82. package/src/blueprint/registry.ts +0 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/assistant",
3
- "version": "0.8.4-main.84f28bd",
3
+ "version": "0.8.4-main.b97322e",
4
4
  "description": "Natural language interface to DXOS",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -21,32 +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.4-main.84f28bd",
29
- "@dxos/artifact": "0.8.4-main.84f28bd",
30
- "@dxos/async": "0.8.4-main.84f28bd",
31
- "@dxos/debug": "0.8.4-main.84f28bd",
32
- "@dxos/echo": "0.8.4-main.84f28bd",
33
- "@dxos/context": "0.8.4-main.84f28bd",
34
- "@dxos/echo-db": "0.8.4-main.84f28bd",
35
- "@dxos/echo-protocol": "0.8.4-main.84f28bd",
36
- "@dxos/echo-schema": "0.8.4-main.84f28bd",
37
- "@dxos/effect": "0.8.4-main.84f28bd",
38
- "@dxos/functions": "0.8.4-main.84f28bd",
39
- "@dxos/graph": "0.8.4-main.84f28bd",
40
- "@dxos/invariant": "0.8.4-main.84f28bd",
41
- "@dxos/keys": "0.8.4-main.84f28bd",
42
- "@dxos/live-object": "0.8.4-main.84f28bd",
43
- "@dxos/log": "0.8.4-main.84f28bd",
44
- "@dxos/node-std": "0.8.4-main.84f28bd",
45
- "@dxos/schema": "0.8.4-main.84f28bd",
46
- "@dxos/util": "0.8.4-main.84f28bd"
32
+ "handlebars": "4.7.8",
33
+ "lodash.defaultsdeep": "^4.6.1",
34
+ "@dxos/ai": "0.8.4-main.b97322e",
35
+ "@dxos/async": "0.8.4-main.b97322e",
36
+ "@dxos/context": "0.8.4-main.b97322e",
37
+ "@dxos/blueprints": "0.8.4-main.b97322e",
38
+ "@dxos/debug": "0.8.4-main.b97322e",
39
+ "@dxos/echo": "0.8.4-main.b97322e",
40
+ "@dxos/echo-db": "0.8.4-main.b97322e",
41
+ "@dxos/effect": "0.8.4-main.b97322e",
42
+ "@dxos/echo-schema": "0.8.4-main.b97322e",
43
+ "@dxos/errors": "0.8.4-main.b97322e",
44
+ "@dxos/functions": "0.8.4-main.b97322e",
45
+ "@dxos/invariant": "0.8.4-main.b97322e",
46
+ "@dxos/graph": "0.8.4-main.b97322e",
47
+ "@dxos/keys": "0.8.4-main.b97322e",
48
+ "@dxos/log": "0.8.4-main.b97322e",
49
+ "@dxos/live-object": "0.8.4-main.b97322e",
50
+ "@dxos/node-std": "0.8.4-main.b97322e",
51
+ "@dxos/schema": "0.8.4-main.b97322e",
52
+ "@dxos/util": "0.8.4-main.b97322e",
53
+ "@dxos/echo-protocol": "0.8.4-main.b97322e"
47
54
  },
48
55
  "devDependencies": {
49
- "effect": "3.16.13",
56
+ "@types/lodash.defaultsdeep": "^4.6.6",
57
+ "effect": "3.17.0",
50
58
  "inquirer": "^12.0.0"
51
59
  },
52
60
  "peerDependencies": {
@@ -5,12 +5,12 @@
5
5
  import { computed, type ReadonlySignal } from '@preact/signals-core';
6
6
  import { Array, pipe } from 'effect';
7
7
 
8
+ import { type Blueprint } from '@dxos/blueprints';
8
9
  import { Obj, type Type, type Ref, type Relation } from '@dxos/echo';
9
10
  import { type Queue } from '@dxos/echo-db';
10
11
  import { ComplexSet } from '@dxos/util';
11
12
 
12
13
  import { ContextBinding } from './binding';
13
- import { type Blueprint } from '../blueprint';
14
14
 
15
15
  /**
16
16
  * Manages bindings of blueprints and objects to a conversation.
@@ -25,7 +25,9 @@ export class ContextBinder {
25
25
  readonly bindings: ReadonlySignal<Bindings> = computed(() => this._reduce(this._queue.objects));
26
26
 
27
27
  // TODO(burdon): load refs?
28
- readonly blueprints: ReadonlySignal<Ref.Ref<Blueprint>[]> = computed(() => [...this.bindings.value.blueprints]);
28
+ readonly blueprints: ReadonlySignal<Ref.Ref<Blueprint.Blueprint>[]> = computed(() => [
29
+ ...this.bindings.value.blueprints,
30
+ ]);
29
31
  readonly objects: ReadonlySignal<Ref.Ref<Type.Expando>[]> = computed(() => [...this.bindings.value.objects]);
30
32
 
31
33
  /**
@@ -91,11 +93,11 @@ export class ContextBinder {
91
93
  }
92
94
 
93
95
  export type BindingProps = Partial<{
94
- blueprints: Ref.Ref<Blueprint>[];
96
+ blueprints: Ref.Ref<Blueprint.Blueprint>[];
95
97
  objects: Ref.Ref<Type.Expando>[];
96
98
  }>;
97
99
 
98
100
  export class Bindings {
99
- blueprints = new ComplexSet<Ref.Ref<Blueprint>>((ref) => ref.dxn.toString());
101
+ blueprints = new ComplexSet<Ref.Ref<Blueprint.Blueprint>>((ref) => ref.dxn.toString());
100
102
  objects = new ComplexSet<Ref.Ref<Type.Expando>>((ref) => ref.dxn.toString());
101
103
  }
@@ -4,16 +4,17 @@
4
4
 
5
5
  import { Schema } from 'effect';
6
6
 
7
+ import { Blueprint } from '@dxos/blueprints';
7
8
  import { Type } from '@dxos/echo';
8
9
 
9
- import { Blueprint } from '../blueprint';
10
10
  /**
11
11
  * Thread message that binds or unbinds contextual objects to a conversation.
12
12
  */
13
+ // TODO(burdon): Make ContentBlock.
13
14
  export const ContextBinding = Schema.Struct({
14
15
  blueprints: Schema.Struct({
15
- added: Schema.Array(Type.Ref(Blueprint)),
16
- removed: Schema.Array(Type.Ref(Blueprint)),
16
+ added: Schema.Array(Type.Ref(Blueprint.Blueprint)),
17
+ removed: Schema.Array(Type.Ref(Blueprint.Blueprint)),
17
18
  }),
18
19
 
19
20
  // TODO(burdon): Type.Expando => Type.Obj (or Obj.Any?)
@@ -2,37 +2,34 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Message, type ExecutableTool } from '@dxos/ai';
6
- import { type ArtifactDefinition } from '@dxos/artifact';
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';
7
14
  import { Event } from '@dxos/async';
8
- import { Obj, Ref } from '@dxos/echo';
15
+ import { Obj } from '@dxos/echo';
9
16
  import { type Queue } from '@dxos/echo-db';
10
- import { AiService, ToolResolverService, type ServiceContainer } from '@dxos/functions';
11
- import { log } from '@dxos/log';
12
- import { isNonNullable } from '@dxos/util';
17
+ import { DatabaseService } from '@dxos/functions';
18
+ import { DataType } from '@dxos/schema';
13
19
 
14
20
  import { ContextBinder, type ContextBinding } from '../context';
15
- import { AISession, type SessionRunOptions } from '../session';
21
+ import type { AiAssistantError } from '../errors';
22
+ import { AiSession } from '../session';
16
23
 
17
- export interface ConversationRunOptions {
24
+ export interface ConversationRunOptions<Tools extends AiTool.Any> {
18
25
  systemPrompt?: string;
19
26
  prompt: string;
20
27
 
21
- tools?: ExecutableTool[];
22
- artifacts?: ArtifactDefinition[];
23
- extensions?: ToolContextExtensions;
24
- generationOptions?: SessionRunOptions['generationOptions'];
25
-
26
- /** @depreacated Should be managed by the conversation. */
27
- requiredArtifactIds?: string[];
28
-
29
- // TODO(dmaretskyi): Move into conversation.
30
- artifactDiffResolver?: SessionRunOptions['artifactDiffResolver'];
28
+ toolkit?: AiToolkit.AiToolkit<Tools>;
31
29
  }
32
30
 
33
31
  export type ConversationOptions = {
34
- serviceContainer: ServiceContainer;
35
- queue: Queue<Message | ContextBinding>;
32
+ queue: Queue<DataType.Message | ContextBinding>;
36
33
  };
37
34
 
38
35
  /**
@@ -41,14 +38,13 @@ export type ConversationOptions = {
41
38
  * Backed by a Queue.
42
39
  */
43
40
  export class Conversation {
44
- private readonly _serviceContainer: ServiceContainer;
45
- private readonly _queue: Queue<Message | ContextBinding>;
41
+ private readonly _queue: Queue<DataType.Message | ContextBinding>;
46
42
 
47
43
  /**
48
44
  * Fired when the execution loop begins.
49
45
  * This is called before the first message is sent.
50
46
  */
51
- public readonly onBegin = new Event<AISession>();
47
+ public readonly onBegin = new Event<AiSession>();
52
48
 
53
49
  /**
54
50
  * Blueprints bound to the conversation.
@@ -56,48 +52,45 @@ export class Conversation {
56
52
  public readonly context: ContextBinder;
57
53
 
58
54
  constructor(options: ConversationOptions) {
59
- this._serviceContainer = options.serviceContainer;
60
55
  this._queue = options.queue;
61
56
  this.context = new ContextBinder(this._queue);
62
57
  }
63
58
 
64
- async run(options: ConversationRunOptions): Promise<Message[]> {
65
- const session = new AISession({ operationModel: 'configured' });
66
- this.onBegin.emit(session);
67
-
68
- const history = await this.getHistory();
69
- const context = await this.context.query();
70
- const blueprints = (await Ref.Array.loadAll([...context.blueprints])).filter(isNonNullable);
71
- if (blueprints.length > 1) {
72
- log.warn('multiple blueprints are not yet supported');
73
- }
74
-
75
- const messages = await session.run({
76
- history,
77
- prompt: options.prompt,
78
- systemPrompt: (options.systemPrompt ?? '') + (blueprints.at(0)?.instructions ?? ''),
79
-
80
- // TODO(dmaretskyi): Artifacts come from the blueprint?
81
- artifacts: options.artifacts ?? [],
82
- tools: blueprints.at(0)?.tools ?? [],
83
- executableTools: options.tools,
84
- requiredArtifactIds: options.requiredArtifactIds,
85
- client: this._serviceContainer.getService(AiService).client,
86
- toolResolver: this._serviceContainer.getService(ToolResolverService).toolResolver,
87
- extensions: {
88
- serviceContainer: this._serviceContainer,
89
- ...options.extensions,
90
- },
91
- generationOptions: options.generationOptions,
92
- });
93
-
94
- await this._queue.append(messages);
95
-
96
- return messages;
97
- }
98
-
99
- async getHistory(): Promise<Message[]> {
59
+ async getHistory(): Promise<DataType.Message[]> {
100
60
  const queueItems = await this._queue.queryObjects();
101
- return queueItems.filter(Obj.instanceOf(Message));
61
+ return queueItems.filter(Obj.instanceOf(DataType.Message));
102
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'));
103
96
  }
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, ToolRegistry } 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,17 +31,18 @@ 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
  ],
@@ -46,13 +50,13 @@ export const extractionAnthropicFn: FunctionDefinition<ExtractionInput, Extracti
46
50
  prompt: '',
47
51
  tools: [],
48
52
  toolResolver: new ToolRegistry([]),
49
- });
53
+ } as any); // TODO(burdon): Rewrite test.
50
54
 
51
55
  return {
52
56
  message: create(DataType.Message, {
53
57
  ...message,
54
58
  blocks: message.blocks.map((block, i) =>
55
- block.type !== 'transcription'
59
+ block._tag !== 'transcript'
56
60
  ? block
57
61
  : {
58
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,11 +2,11 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './blueprint';
6
5
  export * from './context';
7
6
  export * from './conversation';
8
7
  export * from './extraction';
8
+ export * from './functions';
9
9
  export * from './research';
10
10
  export * from './session';
11
+ export * from './templates';
11
12
  export * from './types';
12
- export * from './conversation';