@dxos/assistant 0.8.4-main.fd6878d → 0.8.4-main.fffef41

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 (122) hide show
  1. package/dist/lib/browser/chunk-J5LGTIGS.mjs +10 -0
  2. package/dist/lib/browser/extraction/index.mjs +121 -165
  3. package/dist/lib/browser/extraction/index.mjs.map +4 -4
  4. package/dist/lib/browser/index.mjs +1097 -161
  5. package/dist/lib/browser/index.mjs.map +4 -4
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/node-esm/chunk-HSLMI22Q.mjs +11 -0
  8. package/dist/lib/node-esm/extraction/index.mjs +121 -165
  9. package/dist/lib/node-esm/extraction/index.mjs.map +4 -4
  10. package/dist/lib/node-esm/index.mjs +1097 -161
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/types/src/conversation/context.d.ts +23 -10
  14. package/dist/types/src/conversation/context.d.ts.map +1 -1
  15. package/dist/types/src/conversation/conversation.d.ts +22 -31
  16. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  17. package/dist/types/src/diff.d.ts +15 -0
  18. package/dist/types/src/diff.d.ts.map +1 -0
  19. package/dist/types/src/diff.test.d.ts +2 -0
  20. package/dist/types/src/diff.test.d.ts.map +1 -0
  21. package/dist/types/src/errors.d.ts +45 -13
  22. package/dist/types/src/errors.d.ts.map +1 -1
  23. package/dist/types/src/execution-graph.d.ts +85 -0
  24. package/dist/types/src/execution-graph.d.ts.map +1 -0
  25. package/dist/types/src/execution-graph.test.d.ts +2 -0
  26. package/dist/types/src/execution-graph.test.d.ts.map +1 -0
  27. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts +1 -1
  28. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
  29. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +82 -43
  30. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
  31. package/dist/types/src/extraction/extraction-ner-function/named-entity-recognition.d.ts.map +1 -1
  32. package/dist/types/src/extraction/extraction.d.ts +262 -225
  33. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  34. package/dist/types/src/extraction/quotes.d.ts +1 -1
  35. package/dist/types/src/extraction/quotes.d.ts.map +1 -1
  36. package/dist/types/src/functions/services.d.ts +18 -5
  37. package/dist/types/src/functions/services.d.ts.map +1 -1
  38. package/dist/types/src/index.d.ts +3 -1
  39. package/dist/types/src/index.d.ts.map +1 -1
  40. package/dist/types/src/session/GenericToolkit.d.ts +84 -0
  41. package/dist/types/src/session/GenericToolkit.d.ts.map +1 -0
  42. package/dist/types/src/session/artifact-diff.d.ts +2 -2
  43. package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
  44. package/dist/types/src/session/error-handling.d.ts +4 -0
  45. package/dist/types/src/session/error-handling.d.ts.map +1 -0
  46. package/dist/types/src/session/format.d.ts +5 -161
  47. package/dist/types/src/session/format.d.ts.map +1 -1
  48. package/dist/types/src/session/index.d.ts +3 -0
  49. package/dist/types/src/session/index.d.ts.map +1 -1
  50. package/dist/types/src/session/observer.d.ts +22 -0
  51. package/dist/types/src/session/observer.d.ts.map +1 -0
  52. package/dist/types/src/session/session.d.ts +22 -138
  53. package/dist/types/src/session/session.d.ts.map +1 -1
  54. package/dist/types/src/session/toolkit.d.ts +15 -0
  55. package/dist/types/src/session/toolkit.d.ts.map +1 -0
  56. package/dist/types/src/session/version-pin.d.ts +3 -3
  57. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  58. package/dist/types/src/templates/system.d.ts +8 -12
  59. package/dist/types/src/templates/system.d.ts.map +1 -1
  60. package/dist/types/src/types/artifact.d.ts +20 -0
  61. package/dist/types/src/types/artifact.d.ts.map +1 -0
  62. package/dist/types/src/types/index.d.ts +2 -0
  63. package/dist/types/src/types/index.d.ts.map +1 -0
  64. package/dist/types/tsconfig.tsbuildinfo +1 -1
  65. package/package.json +29 -31
  66. package/src/conversation/context.ts +75 -22
  67. package/src/conversation/conversation.ts +80 -77
  68. package/src/diff.test.ts +43 -0
  69. package/src/diff.ts +84 -0
  70. package/src/errors.ts +6 -1
  71. package/src/execution-graph.test.ts +740 -0
  72. package/src/execution-graph.ts +509 -0
  73. package/src/extraction/extraction-llm-function/extraction-llm-function.test.ts +9 -9
  74. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +47 -56
  75. package/src/extraction/extraction-ner-function/extraction-ner-function.test.ts +16 -16
  76. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +42 -43
  77. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +12 -6
  78. package/src/extraction/extraction-ner-function/named-entity-recognition.ts +2 -0
  79. package/src/extraction/extraction.ts +7 -6
  80. package/src/extraction/quotes.test.ts +2 -2
  81. package/src/extraction/quotes.ts +3 -3
  82. package/src/functions/services.ts +104 -56
  83. package/src/index.ts +3 -1
  84. package/src/session/GenericToolkit.ts +130 -0
  85. package/src/session/artifact-diff.ts +8 -4
  86. package/src/session/error-handling.ts +41 -0
  87. package/src/session/format.test.ts +5 -4
  88. package/src/session/format.ts +27 -22
  89. package/src/session/index.ts +4 -0
  90. package/src/session/observer.ts +59 -0
  91. package/src/session/session.conversations.json +1 -0
  92. package/src/session/session.test.ts +35 -191
  93. package/src/session/session.ts +119 -185
  94. package/src/session/toolkit.ts +48 -0
  95. package/src/session/version-pin.ts +4 -5
  96. package/src/templates/{format.tpl → instructions/format.tpl} +8 -2
  97. package/src/templates/{system.tpl → instructions/system.tpl} +15 -14
  98. package/src/templates/system.ts +25 -16
  99. package/src/{types.ts → types/artifact.ts} +22 -3
  100. package/src/types/index.ts +5 -0
  101. package/dist/lib/browser/chunk-6WRGDEMH.mjs +0 -89
  102. package/dist/lib/browser/chunk-6WRGDEMH.mjs.map +0 -7
  103. package/dist/lib/browser/chunk-NZGGCEF3.mjs +0 -341
  104. package/dist/lib/browser/chunk-NZGGCEF3.mjs.map +0 -7
  105. package/dist/lib/browser/named-entity-recognition-O46UTCLR.mjs +0 -13
  106. package/dist/lib/node-esm/chunk-3UU5V4FS.mjs +0 -91
  107. package/dist/lib/node-esm/chunk-3UU5V4FS.mjs.map +0 -7
  108. package/dist/lib/node-esm/chunk-XOOPGSGW.mjs +0 -343
  109. package/dist/lib/node-esm/chunk-XOOPGSGW.mjs.map +0 -7
  110. package/dist/lib/node-esm/named-entity-recognition-W4VMRCH6.mjs +0 -14
  111. package/dist/types/src/session/version-pin.test.d.ts +0 -2
  112. package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
  113. package/dist/types/src/summarization/summarization.test.d.ts +0 -2
  114. package/dist/types/src/summarization/summarization.test.d.ts.map +0 -1
  115. package/dist/types/src/types.d.ts +0 -16
  116. package/dist/types/src/types.d.ts.map +0 -1
  117. package/src/session/__snapshots__/session.test.ts.snap +0 -32
  118. package/src/session/version-pin.test.ts +0 -7
  119. package/src/summarization/summarization.test.ts +0 -197
  120. /package/dist/lib/browser/{named-entity-recognition-O46UTCLR.mjs.map → chunk-J5LGTIGS.mjs.map} +0 -0
  121. /package/dist/lib/node-esm/{named-entity-recognition-W4VMRCH6.mjs.map → chunk-HSLMI22Q.mjs.map} +0 -0
  122. /package/src/extraction/extraction-llm-function/{instructions.tpl → transcription-instructions.tpl} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/assistant",
3
- "version": "0.8.4-main.fd6878d",
3
+ "version": "0.8.4-main.fffef41",
4
4
  "description": "Natural language interface to DXOS",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -38,40 +38,38 @@
38
38
  "src"
39
39
  ],
40
40
  "dependencies": {
41
- "@effect/ai": "0.25.2",
42
- "@effect/ai-anthropic": "^0.15.1",
43
- "@effect/platform": "^0.90.2",
44
- "@effect/platform-node": "^0.95.0",
45
- "@preact/signals-core": "^1.9.0",
41
+ "@automerge/automerge": "3.1.2",
42
+ "@effect/ai": "0.29.1",
43
+ "@effect/experimental": "0.56.0",
44
+ "@effect/platform": "^0.92.1",
45
+ "@preact/signals-core": "^1.12.1",
46
46
  "@xenova/transformers": "^2.17.2",
47
- "chalk": "^4.1.0",
48
- "handlebars": "4.7.8",
49
47
  "lodash.defaultsdeep": "^4.6.1",
50
- "@dxos/async": "0.8.4-main.fd6878d",
51
- "@dxos/context": "0.8.4-main.fd6878d",
52
- "@dxos/blueprints": "0.8.4-main.fd6878d",
53
- "@dxos/debug": "0.8.4-main.fd6878d",
54
- "@dxos/echo-db": "0.8.4-main.fd6878d",
55
- "@dxos/echo": "0.8.4-main.fd6878d",
56
- "@dxos/echo-protocol": "0.8.4-main.fd6878d",
57
- "@dxos/echo-schema": "0.8.4-main.fd6878d",
58
- "@dxos/errors": "0.8.4-main.fd6878d",
59
- "@dxos/functions": "0.8.4-main.fd6878d",
60
- "@dxos/effect": "0.8.4-main.fd6878d",
61
- "@dxos/invariant": "0.8.4-main.fd6878d",
62
- "@dxos/graph": "0.8.4-main.fd6878d",
63
- "@dxos/ai": "0.8.4-main.fd6878d",
64
- "@dxos/keys": "0.8.4-main.fd6878d",
65
- "@dxos/log": "0.8.4-main.fd6878d",
66
- "@dxos/node-std": "0.8.4-main.fd6878d",
67
- "@dxos/schema": "0.8.4-main.fd6878d",
68
- "@dxos/util": "0.8.4-main.fd6878d",
69
- "@dxos/live-object": "0.8.4-main.fd6878d"
48
+ "@dxos/ai": "0.8.4-main.fffef41",
49
+ "@dxos/async": "0.8.4-main.fffef41",
50
+ "@dxos/blueprints": "0.8.4-main.fffef41",
51
+ "@dxos/echo": "0.8.4-main.fffef41",
52
+ "@dxos/debug": "0.8.4-main.fffef41",
53
+ "@dxos/echo-db": "0.8.4-main.fffef41",
54
+ "@dxos/echo-protocol": "0.8.4-main.fffef41",
55
+ "@dxos/context": "0.8.4-main.fffef41",
56
+ "@dxos/effect": "0.8.4-main.fffef41",
57
+ "@dxos/errors": "0.8.4-main.fffef41",
58
+ "@dxos/functions": "0.8.4-main.fffef41",
59
+ "@dxos/functions-runtime": "0.8.4-main.fffef41",
60
+ "@dxos/graph": "0.8.4-main.fffef41",
61
+ "@dxos/invariant": "0.8.4-main.fffef41",
62
+ "@dxos/keys": "0.8.4-main.fffef41",
63
+ "@dxos/live-object": "0.8.4-main.fffef41",
64
+ "@dxos/node-std": "0.8.4-main.fffef41",
65
+ "@dxos/schema": "0.8.4-main.fffef41",
66
+ "@dxos/log": "0.8.4-main.fffef41",
67
+ "@dxos/types": "0.8.4-main.fffef41",
68
+ "@dxos/util": "0.8.4-main.fffef41"
70
69
  },
71
70
  "devDependencies": {
72
71
  "@types/lodash.defaultsdeep": "^4.6.6",
73
- "effect": "3.17.7",
74
- "inquirer": "^12.0.0"
72
+ "effect": "3.18.3"
75
73
  },
76
74
  "peerDependencies": {
77
75
  "effect": "^3.13.3"
@@ -81,6 +79,6 @@
81
79
  },
82
80
  "beast": {},
83
81
  "scripts": {
84
- "run:chat": "tsx ./bin/chat.ts"
82
+ "run:chat": "vite-node ./bin/chat.ts"
85
83
  }
86
84
  }
@@ -3,18 +3,22 @@
3
3
  //
4
4
 
5
5
  import { type ReadonlySignal, computed } from '@preact/signals-core';
6
- import { Schema } from 'effect';
7
- import { Array, pipe } from 'effect';
6
+ import * as Array from 'effect/Array';
7
+ import * as Context from 'effect/Context';
8
+ import * as Function from 'effect/Function';
9
+ import * as Schema from 'effect/Schema';
8
10
 
9
11
  import { Blueprint } from '@dxos/blueprints';
10
- import { Obj, type Ref, type Relation, Type } from '@dxos/echo';
12
+ import { Resource } from '@dxos/context';
13
+ import { DXN, Filter, Obj, Query, type Ref, type Relation, Type } from '@dxos/echo';
11
14
  import { type Queue } from '@dxos/echo-db';
15
+ import { invariant } from '@dxos/invariant';
12
16
  import { ComplexSet } from '@dxos/util';
13
17
 
14
18
  /**
15
19
  * Thread message that binds or unbinds contextual objects to a conversation.
16
20
  */
17
- // TODO(burdon): Move to @dxos/schema ContentBlock.
21
+ // TODO(burdon): Move to @dxos/schema ContentBlock?
18
22
  export const ContextBinding = Schema.Struct({
19
23
  blueprints: Schema.Struct({
20
24
  added: Schema.Array(Type.Ref(Blueprint.Blueprint)),
@@ -42,7 +46,8 @@ export type BindingProps = Partial<{
42
46
 
43
47
  export class Bindings {
44
48
  readonly blueprints = new ComplexSet<Ref.Ref<Blueprint.Blueprint>>((ref) => ref.dxn.toString());
45
- readonly objects = new ComplexSet<Ref.Ref<Type.Expando>>((ref) => ref.dxn.toString());
49
+ // TODO(burdon): Some DXNs have the Space prefix so only compare the object ID.
50
+ readonly objects = new ComplexSet<Ref.Ref<Type.Expando>>((ref) => ref.dxn.asEchoDXN()?.echoId);
46
51
 
47
52
  toJSON() {
48
53
  return {
@@ -55,43 +60,78 @@ export class Bindings {
55
60
  /**
56
61
  * Manages bindings of blueprints and objects to a conversation.
57
62
  */
58
- export class AiContextBinder {
63
+ // TODO(burdon): Context should manage ephemeral state of bindings until prompt is issued?
64
+ export class AiContextBinder extends Resource {
59
65
  /**
60
66
  * Reactive query of all bindings.
61
67
  */
62
68
  // TODO(burdon): Cache value?
63
- readonly bindings: ReadonlySignal<Bindings> = computed(() => this._reduce(this._queue.objects));
69
+ private _bindings?: ReadonlySignal<Bindings>;
70
+ private _blueprints?: ReadonlySignal<Ref.Ref<Blueprint.Blueprint>[]>;
71
+ private _objects?: ReadonlySignal<Ref.Ref<Type.Expando>[]>;
64
72
 
65
- readonly blueprints: ReadonlySignal<Ref.Ref<Blueprint.Blueprint>[]> = computed(() => [
66
- ...this.bindings.value.blueprints,
67
- ]);
73
+ constructor(private readonly _queue: Queue) {
74
+ super();
75
+ }
76
+
77
+ get bindings() {
78
+ invariant(this._bindings, 'AiContextBinder not open');
79
+ return this._bindings;
80
+ }
68
81
 
69
- readonly objects: ReadonlySignal<Ref.Ref<Type.Expando>[]> = computed(() => [...this.bindings.value.objects]);
82
+ get blueprints() {
83
+ invariant(this._blueprints, 'AiContextBinder not open');
84
+ return this._blueprints;
85
+ }
70
86
 
71
- constructor(private readonly _queue: Queue) {}
87
+ get objects() {
88
+ invariant(this._objects, 'AiContextBinder not open');
89
+ return this._objects;
90
+ }
91
+
92
+ // TODO(wittjosiah): Use parent context?
93
+ protected override async _open(): Promise<void> {
94
+ const query = this._queue.query(Query.select(Filter.everything()));
95
+ this._ctx.onDispose(query.subscribe(() => {}));
96
+ this._bindings = computed(() => this._reduce(query.objects));
97
+ this._blueprints = computed(() => [...this.bindings.value.blueprints]);
98
+ this._objects = computed(() => [...this.bindings.value.objects]);
99
+ }
100
+
101
+ protected override async _close(): Promise<void> {
102
+ this._bindings = undefined;
103
+ this._blueprints = undefined;
104
+ this._objects = undefined;
105
+ }
72
106
 
73
107
  /**
74
108
  * Asynchronous query of all bindings.
75
109
  */
76
110
  async query(): Promise<Bindings> {
77
- const queueItems = await this._queue.queryObjects();
78
- return this._reduce(queueItems);
111
+ const { objects } = await this._queue.query(Query.select(Filter.everything())).run();
112
+ return this._reduce(objects);
79
113
  }
80
114
 
81
115
  // TODO(burdon): Pass in Blueprint obj (from registry?) and create reference.
82
116
  async bind(props: BindingProps): Promise<void> {
83
- if (!props.blueprints?.length && !props.objects?.length) {
117
+ const blueprints =
118
+ props.blueprints?.filter((ref) => !this.blueprints.peek().find((b) => b.dxn.toString() === ref.dxn.toString())) ??
119
+ [];
120
+ const objects =
121
+ props.objects?.filter((ref) => !this.objects.peek().find((o) => o.dxn.toString() === ref.dxn.toString())) ?? [];
122
+
123
+ if (!blueprints.length && !objects.length) {
84
124
  return;
85
125
  }
86
126
 
87
127
  await this._queue.append([
88
128
  Obj.make(ContextBinding, {
89
129
  blueprints: {
90
- added: props.blueprints ?? [],
130
+ added: blueprints,
91
131
  removed: [],
92
132
  },
93
133
  objects: {
94
- added: props.objects ?? [],
134
+ added: objects,
95
135
  removed: [],
96
136
  },
97
137
  }),
@@ -118,16 +158,29 @@ export class AiContextBinder {
118
158
  }
119
159
 
120
160
  private _reduce(items: (Obj.Any | Relation.Any)[]): Bindings {
121
- return pipe(
161
+ return Function.pipe(
122
162
  items,
123
163
  Array.filter(Obj.instanceOf(ContextBinding)),
124
164
  Array.reduce(new Bindings(), (context, item) => {
125
- item.blueprints.removed.forEach((item) => context.blueprints.delete(item));
126
- item.blueprints.added.forEach((item) => context.blueprints.add(item));
127
- item.objects.removed.forEach((item) => context.objects.delete(item));
128
- item.objects.added.forEach((item) => context.objects.add(item));
165
+ item.blueprints.removed.forEach((ref) => context.blueprints.delete(ref));
166
+ item.blueprints.added.forEach((ref) => context.blueprints.add(ref));
167
+ item.objects.removed.forEach((ref) => {
168
+ for (const obj of context.objects) {
169
+ if (DXN.equalsEchoId(obj.dxn, ref.dxn)) {
170
+ context.objects.delete(obj);
171
+ }
172
+ }
173
+ });
174
+ item.objects.added.forEach((ref) => context.objects.add(ref));
129
175
  return context;
130
176
  }),
131
177
  );
132
178
  }
133
179
  }
180
+
181
+ export class AiContextService extends Context.Tag('@dxos/assistant/AiContextService')<
182
+ AiContextService,
183
+ {
184
+ binder: AiContextBinder;
185
+ }
186
+ >() {}
@@ -2,124 +2,127 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type AiError, type AiLanguageModel, type AiTool, type AiToolkit } from '@effect/ai';
6
- import { Effect } from 'effect';
5
+ import type * as Toolkit from '@effect/ai/Toolkit';
6
+ import * as Array from 'effect/Array';
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Option from 'effect/Option';
7
9
 
8
- import {
9
- type AiInputPreprocessingError,
10
- type AiToolNotFoundError,
11
- type ToolExecutionService,
12
- type ToolResolverService,
13
- } from '@dxos/ai';
14
- import { Event } from '@dxos/async';
10
+ import { Resource } from '@dxos/context';
15
11
  import { Obj } from '@dxos/echo';
16
12
  import { type Queue } from '@dxos/echo-db';
17
- import { DatabaseService } from '@dxos/functions';
13
+ import { DatabaseService } from '@dxos/echo-db';
18
14
  import { log } from '@dxos/log';
19
- import { DataType } from '@dxos/schema';
15
+ import { Message } from '@dxos/types';
20
16
 
21
- import { type AiAssistantError } from '../errors';
22
- import { AiSession, type GenerationObserver } from '../session';
17
+ import {
18
+ AiSession,
19
+ type AiSessionRunError,
20
+ type AiSessionRunRequirements,
21
+ type GenerationObserver,
22
+ createToolkit,
23
+ } from '../session';
23
24
 
24
- import { AiContextBinder, type ContextBinding } from './context';
25
+ import { AiContextBinder, AiContextService, type ContextBinding } from './context';
25
26
 
26
- export interface AiConversationRunParams<Tools extends AiTool.Any> {
27
+ export interface AiConversationRunParams {
27
28
  prompt: string;
28
29
  system?: string;
29
- toolkit?: AiToolkit.AiToolkit<Tools>;
30
30
  observer?: GenerationObserver;
31
-
32
- /**
33
- * @deprecated Remove
34
- */
35
- session?: AiSession;
36
31
  }
37
32
 
38
- export type AiConversationOptions = {
39
- queue: Queue<DataType.Message | ContextBinding>;
40
- };
41
-
42
33
  /**
43
- * Persistent conversation state.
44
- * Context + history + artifacts.
45
- * Backed by a Queue.
34
+ * Durable conversation state (initiated by users and agents) backed by a Queue.
35
+ * Executes tools based on AI responses and supports cancellation of in-progress requests.
46
36
  */
47
- export class AiConversation {
48
- private readonly _queue: Queue<DataType.Message | ContextBinding>;
37
+ export class AiConversation extends Resource {
38
+ /**
39
+ * Message and binding queue.
40
+ */
41
+ private readonly _queue: Queue<Message.Message | ContextBinding>;
49
42
 
50
43
  /**
51
44
  * Blueprints bound to the conversation.
52
45
  */
53
- public readonly _context: AiContextBinder;
46
+ private readonly _context: AiContextBinder;
54
47
 
55
48
  /**
56
- * Fired when the execution loop begins.
57
- * This is called before the first message is sent.
58
- *
59
- * @deprecated Pass in a session instead.
49
+ * Toolkit from the current session request.
60
50
  */
61
- // TODO(burdon): Is this still deprecated?
62
- public readonly onBegin = new Event<AiSession>();
51
+ private _toolkit: Toolkit.WithHandler<any> | undefined;
63
52
 
64
- constructor(options: AiConversationOptions) {
65
- this._queue = options.queue;
53
+ public constructor(queue: Queue<Message.Message | ContextBinding>) {
54
+ super();
55
+ this._queue = queue;
66
56
  this._context = new AiContextBinder(this._queue);
67
57
  }
68
58
 
69
- get context() {
59
+ protected override async _open(): Promise<void> {
60
+ // TODO(wittjosiah): Pass in parent context?
61
+ await this._context.open();
62
+ }
63
+
64
+ protected override async _close(): Promise<void> {
65
+ await this._context.close();
66
+ }
67
+
68
+ public get context() {
70
69
  return this._context;
71
70
  }
72
71
 
73
- async getHistory(): Promise<DataType.Message[]> {
72
+ public get toolkit() {
73
+ return this._toolkit;
74
+ }
75
+
76
+ public async getHistory(): Promise<Message.Message[]> {
74
77
  const queueItems = await this._queue.queryObjects();
75
- return queueItems.filter(Obj.instanceOf(DataType.Message));
78
+ return queueItems.filter(Obj.instanceOf(Message.Message));
76
79
  }
77
80
 
78
81
  /**
79
- * Executes a prompt.
80
- * Each invocation creates a new `AiSession`, which handles potential tool calls.
82
+ * Creates a new cancelable request effect.
81
83
  */
82
- run = <Tools extends AiTool.Any>({
83
- // TODO(burdon): Decide whether to pass in or to fully encapsulate.
84
- session = new AiSession(),
85
- ...params
86
- }: AiConversationRunParams<Tools>): Effect.Effect<
87
- DataType.Message[],
88
- AiAssistantError | AiInputPreprocessingError | AiError.AiError | AiToolNotFoundError,
89
- AiLanguageModel.AiLanguageModel | ToolResolverService | ToolExecutionService | AiTool.ToHandler<Tools>
90
- > =>
91
- Effect.gen(this, function* () {
84
+ createRequest(
85
+ params: AiConversationRunParams,
86
+ ): Effect.Effect<Message.Message[], AiSessionRunError, AiSessionRunRequirements> {
87
+ return Effect.gen(this, function* () {
88
+ const session = new AiSession();
92
89
  const history = yield* Effect.promise(() => this.getHistory());
93
- const context = yield* Effect.promise(() => this.context.query());
94
- const blueprints = yield* Effect.forEach(context.blueprints.values(), DatabaseService.load);
95
90
 
96
- // TODO(burdon): These don't need to be loaded; just need id and typename from context.
97
- const objects = yield* Effect.forEach(context.objects.values(), DatabaseService.load);
91
+ // Get context objects.
92
+ const context = yield* Effect.promise(() => this.context.query());
93
+ const blueprints = yield* Effect.forEach(context.blueprints.values(), DatabaseService.loadOption).pipe(
94
+ Effect.map(Array.filter(Option.isSome)),
95
+ Effect.map(Array.map((option) => option.value)),
96
+ );
97
+ const objects = yield* Effect.forEach(context.objects.values(), DatabaseService.loadOption).pipe(
98
+ Effect.map(Array.filter(Option.isSome)),
99
+ Effect.map(Array.map((option) => option.value)),
100
+ );
101
+
102
+ // Create toolkit.
103
+ const toolkit = yield* createToolkit({ blueprints });
104
+ this._toolkit = toolkit;
98
105
 
99
- log.info('run', {
100
- prompt: params.prompt,
101
- system: params.system,
106
+ const start = Date.now();
107
+ log('run', {
102
108
  history: history.length,
103
- objects: objects.length,
104
109
  blueprints: blueprints.length,
105
- toolkit: params.toolkit,
110
+ objects: objects.length,
111
+ tools: this._toolkit?.tools.length ?? 0,
106
112
  });
107
113
 
108
- const start = Date.now();
109
- this.onBegin.emit(session);
110
-
111
- const messages = yield* session.run({
112
- prompt: params.prompt,
113
- system: params.system,
114
- history,
115
- objects,
116
- blueprints,
117
- toolkit: params.toolkit,
118
- observer: params.observer,
119
- });
114
+ // Process request.
115
+ const messages = yield* session.run({ history, blueprints, objects, toolkit, ...params }).pipe(
116
+ Effect.provideService(AiContextService, {
117
+ binder: this.context,
118
+ }),
119
+ );
120
120
 
121
- log.info('result', { messages: messages, duration: Date.now() - start });
121
+ log('result', { messages: messages.length, duration: Date.now() - start });
122
+
123
+ // Append to queue.
122
124
  yield* Effect.promise(() => this._queue.append(messages));
123
125
  return messages;
124
- }).pipe(Effect.withSpan('AiConversation.run'));
126
+ }).pipe(Effect.withSpan('AiConversation.request'));
127
+ }
125
128
  }
@@ -0,0 +1,43 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import { createDocAccessor } from '@dxos/echo-db';
8
+ import { EchoTestBuilder } from '@dxos/echo-db/testing';
9
+ import { Text } from '@dxos/schema';
10
+ import { trim } from '@dxos/util';
11
+
12
+ import { applyDiffs } from './diff';
13
+
14
+ describe('diff', () => {
15
+ it('should apply diffs', async () => {
16
+ const builder = new EchoTestBuilder();
17
+ const { db, graph } = await builder.createDatabase();
18
+ graph.schemaRegistry.addSchema([Text.Text]);
19
+
20
+ const document = trim`
21
+ # Hello World
22
+ There'z a typo in this sentence.
23
+ And another oon here.
24
+ But not this one.
25
+ `;
26
+
27
+ const text = db.add(Text.make(document));
28
+ const accessor = createDocAccessor(text, ['content']);
29
+ const result = applyDiffs(accessor, [
30
+ `- There'z a typo in this sentence.`,
31
+ `+ There is a typo in this sentence.`,
32
+ `- And another oon here.`,
33
+ `+ And another one here.`,
34
+ ]);
35
+
36
+ expect(result).toBe(trim`
37
+ # Hello World
38
+ There is a typo in this sentence.
39
+ And another one here.
40
+ But not this one.
41
+ `);
42
+ });
43
+ });
package/src/diff.ts ADDED
@@ -0,0 +1,84 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { next as A, type Doc } from '@automerge/automerge';
6
+
7
+ import { DocAccessor, toCursorRange } from '@dxos/echo-db';
8
+ import { log } from '@dxos/log';
9
+ import { isNonNullable } from '@dxos/util';
10
+
11
+ // Relevant info:
12
+ // https://github.com/Minahil-official/GPT-4.1-Promting-Guide?tab=readme-ov-file#-apply-patch-tool-format-v4a-diff
13
+
14
+ // TODO(burdon): Anthropic only.
15
+ // https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/text-editor-tool#example-str-replace-command
16
+ // AI_TOOL_NOT_FOUND: str_replace_based_edit_tool
17
+ // 'str_replace_based_edit_tool',
18
+
19
+ // TOOD(burdon): Support append to end (no "-").
20
+
21
+ export type Diff = {
22
+ match: string;
23
+ replace: string;
24
+ };
25
+
26
+ /**
27
+ * Convert a list of diffs into a list of operations.
28
+ */
29
+ export const reduceDiffs = (diffs: readonly string[]): Diff[] => {
30
+ return diffs.reduce<Diff[]>((acc, diff) => {
31
+ const current = acc[acc.length - 1];
32
+ const [op, ...rest] = diff.split(' ');
33
+ switch (op) {
34
+ case '-': {
35
+ if (current && !current.match) {
36
+ current.match += rest.join(' ');
37
+ } else {
38
+ acc.push({ match: rest.join(' '), replace: '' });
39
+ }
40
+ return acc;
41
+ }
42
+ case '+': {
43
+ if (current && !current.replace) {
44
+ current.replace += rest.join(' ');
45
+ } else {
46
+ acc.push({ match: '', replace: rest.join(' ') });
47
+ }
48
+ return acc;
49
+ }
50
+ }
51
+
52
+ return acc;
53
+ }, []);
54
+ };
55
+
56
+ export const computeDiffsWithCursors = <T>(accessor: DocAccessor<T>, diffs: readonly string[]) => {
57
+ return reduceDiffs(diffs)
58
+ .map((diff) => {
59
+ const text = DocAccessor.getValue<string>(accessor);
60
+ const idx = text.indexOf(diff.match);
61
+ if (idx !== -1) {
62
+ return { cursor: toCursorRange(accessor, idx, idx + diff.match.length), text: diff.replace };
63
+ }
64
+ })
65
+ .filter(isNonNullable);
66
+ };
67
+
68
+ export const applyDiffs = <T>(accessor: DocAccessor<T>, diffs: readonly string[]): string => {
69
+ for (const diff of reduceDiffs(diffs)) {
70
+ accessor.handle.change((doc: Doc<T>) => {
71
+ const text = DocAccessor.getValue<string>(accessor);
72
+ const idx = text.indexOf(diff.match);
73
+ if (idx !== -1) {
74
+ // TODO(burdon): Replace smallest substring.
75
+ // NOTE: This only replaces the first match.
76
+ A.splice(doc, accessor.path as A.Prop[], idx, diff.match.length, diff.replace);
77
+ } else {
78
+ log.warn('diff not found', diff);
79
+ }
80
+ });
81
+ }
82
+
83
+ return DocAccessor.getValue<string>(accessor);
84
+ };
package/src/errors.ts CHANGED
@@ -4,7 +4,12 @@
4
4
 
5
5
  import { BaseError } from '@dxos/errors';
6
6
 
7
+ /**
8
+ * Generic error from AI model.
9
+ */
10
+ export class AiModelError extends BaseError.extend('AI_MODEL_ERROR', 'AI model error') {}
11
+
7
12
  /**
8
13
  * Generic error for AI agent execution.
9
14
  */
10
- export class AiAssistantError extends BaseError.extend('AI_ASSISTANT_ERROR') {}
15
+ export class AiAssistantError extends BaseError.extend('AI_ASSISTANT_ERROR', 'AI assistant error') {}