@dxos/assistant 0.8.4-main.e098934 → 0.8.4-main.ead640a

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 (86) hide show
  1. package/dist/lib/browser/{chunk-3K5SRE5A.mjs → chunk-CWGVG235.mjs} +59 -83
  2. package/dist/lib/browser/chunk-CWGVG235.mjs.map +7 -0
  3. package/dist/lib/browser/extraction/index.mjs +17 -13
  4. package/dist/lib/browser/extraction/index.mjs.map +4 -4
  5. package/dist/lib/browser/index.mjs +550 -86
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/node-esm/{chunk-W7AIB6DP.mjs → chunk-OV5PMQPB.mjs} +59 -83
  9. package/dist/lib/node-esm/chunk-OV5PMQPB.mjs.map +7 -0
  10. package/dist/lib/node-esm/extraction/index.mjs +17 -13
  11. package/dist/lib/node-esm/extraction/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/index.mjs +550 -86
  13. package/dist/lib/node-esm/index.mjs.map +4 -4
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/types/src/conversation/context.d.ts +14 -7
  16. package/dist/types/src/conversation/context.d.ts.map +1 -1
  17. package/dist/types/src/conversation/conversation.d.ts +9 -10
  18. package/dist/types/src/conversation/conversation.d.ts.map +1 -1
  19. package/dist/types/src/errors.d.ts +8 -8
  20. package/dist/types/src/execution-graph.d.ts +82 -0
  21. package/dist/types/src/execution-graph.d.ts.map +1 -0
  22. package/dist/types/src/execution-graph.test.d.ts +2 -0
  23. package/dist/types/src/execution-graph.test.d.ts.map +1 -0
  24. package/dist/types/src/extraction/extraction-llm-function/extraction-llm-function.d.ts.map +1 -1
  25. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts +61 -57
  26. package/dist/types/src/extraction/extraction-ner-function/extraction-ner-function.d.ts.map +1 -1
  27. package/dist/types/src/extraction/extraction.d.ts +10 -6
  28. package/dist/types/src/extraction/extraction.d.ts.map +1 -1
  29. package/dist/types/src/extraction/quotes.d.ts +1 -1
  30. package/dist/types/src/extraction/quotes.d.ts.map +1 -1
  31. package/dist/types/src/functions/services.d.ts +17 -5
  32. package/dist/types/src/functions/services.d.ts.map +1 -1
  33. package/dist/types/src/index.d.ts +2 -1
  34. package/dist/types/src/index.d.ts.map +1 -1
  35. package/dist/types/src/session/artifact-diff.d.ts +2 -2
  36. package/dist/types/src/session/artifact-diff.d.ts.map +1 -1
  37. package/dist/types/src/session/error-handling.d.ts +2 -2
  38. package/dist/types/src/session/error-handling.d.ts.map +1 -1
  39. package/dist/types/src/session/format.d.ts +33 -31
  40. package/dist/types/src/session/format.d.ts.map +1 -1
  41. package/dist/types/src/session/observer.d.ts +1 -1
  42. package/dist/types/src/session/observer.d.ts.map +1 -1
  43. package/dist/types/src/session/session.d.ts +13 -9
  44. package/dist/types/src/session/session.d.ts.map +1 -1
  45. package/dist/types/src/session/toolkit.d.ts +7 -6
  46. package/dist/types/src/session/toolkit.d.ts.map +1 -1
  47. package/dist/types/src/session/version-pin.d.ts +2 -2
  48. package/dist/types/src/session/version-pin.d.ts.map +1 -1
  49. package/dist/types/src/{types.d.ts → types/artifact.d.ts} +3 -3
  50. package/dist/types/src/types/artifact.d.ts.map +1 -0
  51. package/dist/types/src/types/index.d.ts +2 -0
  52. package/dist/types/src/types/index.d.ts.map +1 -0
  53. package/dist/types/tsconfig.tsbuildinfo +1 -1
  54. package/package.json +26 -31
  55. package/src/conversation/context.ts +49 -17
  56. package/src/conversation/conversation.ts +23 -18
  57. package/src/execution-graph.test.ts +738 -0
  58. package/src/execution-graph.ts +500 -0
  59. package/src/extraction/extraction-llm-function/extraction-llm-function.ts +3 -1
  60. package/src/extraction/extraction-ner-function/extraction-ner-function.ts +3 -1
  61. package/src/extraction/extraction-ner-function/named-entity-recognition.test.ts +2 -1
  62. package/src/extraction/extraction.ts +2 -2
  63. package/src/extraction/quotes.ts +2 -2
  64. package/src/functions/services.ts +85 -46
  65. package/src/index.ts +2 -1
  66. package/src/session/artifact-diff.ts +2 -2
  67. package/src/session/error-handling.ts +6 -4
  68. package/src/session/format.test.ts +1 -1
  69. package/src/session/format.ts +8 -5
  70. package/src/session/observer.ts +2 -1
  71. package/src/session/session.conversations.json +1 -0
  72. package/src/session/session.test.ts +50 -209
  73. package/src/session/session.ts +31 -20
  74. package/src/session/toolkit.ts +19 -13
  75. package/src/session/version-pin.ts +2 -2
  76. package/src/summarization/summarization.test.ts +8 -3
  77. package/src/templates/system.tpl +10 -12
  78. package/src/{types.ts → types/artifact.ts} +2 -2
  79. package/src/types/index.ts +5 -0
  80. package/dist/lib/browser/chunk-3K5SRE5A.mjs.map +0 -7
  81. package/dist/lib/node-esm/chunk-W7AIB6DP.mjs.map +0 -7
  82. package/dist/types/src/session/version-pin.test.d.ts +0 -2
  83. package/dist/types/src/session/version-pin.test.d.ts.map +0 -1
  84. package/dist/types/src/types.d.ts.map +0 -1
  85. package/src/session/__snapshots__/session.test.ts.snap +0 -32
  86. package/src/session/version-pin.test.ts +0 -7
@@ -2,97 +2,132 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { AiTool, type AiToolkit } from '@effect/ai';
6
- import { Context, Effect, Layer, Record, Schema } from 'effect';
5
+ import * as Tool from '@effect/ai/Tool';
6
+ import type * as Toolkit from '@effect/ai/Toolkit';
7
+ import * as Context from 'effect/Context';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Layer from 'effect/Layer';
10
+ import * as Record from 'effect/Record';
11
+ import * as Schema from 'effect/Schema';
7
12
 
8
13
  import { AiToolNotFoundError, ToolExecutionService, ToolResolverService } from '@dxos/ai';
9
14
  import { todo } from '@dxos/debug';
10
- import { type FunctionDefinition, LocalFunctionExecutionService } from '@dxos/functions';
15
+ import { Query } from '@dxos/echo';
16
+ import { DatabaseService, FunctionDefinition, FunctionInvocationService, FunctionType } from '@dxos/functions';
11
17
  import { invariant } from '@dxos/invariant';
12
18
 
19
+ /**
20
+ * Constructs a `ToolResolverService` whose `resolve(id)` looks up tools in the following order:
21
+ * 1. Toolkit: return an existing tool from the provided `toolkit` if one is present under `id`.
22
+ * 2. Functions in DB: query `FunctionType` by `key=id`; if found, deserialize and project to a tool
23
+ * (propagating any `deployedFunctionId` discovered in the object's metadata).
24
+ * 3. Functions passed in: fall back to a matching `FunctionDefinition` from the `functions` array.
25
+ *
26
+ * If none of the above yield a match, the effect fails with `AiToolNotFoundError`.
27
+ *
28
+ * Requires `DatabaseService` in the environment.
29
+ */
13
30
  export const makeToolResolverFromFunctions = (
14
31
  functions: FunctionDefinition<any, any>[],
15
- toolkit: AiToolkit.Any,
16
- ): Layer.Layer<ToolResolverService> => {
17
- return Layer.succeed(ToolResolverService, {
18
- resolve: Effect.fn('resolveTool')(function* (id) {
19
- const tool = toolkit.tools[id];
20
- if (tool) {
21
- return tool;
22
- }
23
-
24
- const functionDef = functions.find((fn) => fn.name === id);
25
- if (!functionDef) {
26
- return yield* Effect.fail(new AiToolNotFoundError(id));
27
- }
28
-
29
- return projectFunctionToTool(functionDef);
32
+ toolkit: Toolkit.Toolkit<any>,
33
+ ): Layer.Layer<ToolResolverService, never, DatabaseService> => {
34
+ return Layer.effect(
35
+ ToolResolverService,
36
+ Effect.gen(function* () {
37
+ const dbService = yield* DatabaseService;
38
+ return {
39
+ resolve: (id): Effect.Effect<Tool.Any, AiToolNotFoundError> =>
40
+ Effect.gen(function* () {
41
+ const tool = toolkit.tools[id];
42
+ if (tool) {
43
+ return tool;
44
+ }
45
+
46
+ const {
47
+ objects: [dbFunction],
48
+ } = yield* DatabaseService.runQuery(Query.type(FunctionType, { key: id }));
49
+
50
+ const functionDef = dbFunction
51
+ ? FunctionDefinition.deserialize(dbFunction)
52
+ : functions.find((fn) => fn.key === id);
53
+
54
+ if (!functionDef) {
55
+ return yield* Effect.fail(new AiToolNotFoundError(id));
56
+ }
57
+
58
+ return projectFunctionToTool(functionDef);
59
+ }).pipe(Effect.provideService(DatabaseService, dbService)),
60
+ } satisfies Context.Tag.Service<ToolResolverService>;
30
61
  }),
31
- });
62
+ );
32
63
  };
33
64
 
34
65
  export const makeToolExecutionServiceFromFunctions = (
35
- functions: FunctionDefinition<any, any>[],
36
- toolkit: AiToolkit.AiToolkit<AiTool.Any>,
37
- handlersLayer: Layer.Layer<AiTool.ToHandler<AiTool.AiTool<any>>, never, never>,
38
- ): Layer.Layer<ToolExecutionService, never, LocalFunctionExecutionService> => {
66
+ toolkit: Toolkit.Toolkit<any>,
67
+ handlersLayer: Layer.Layer<Tool.Handler<any>, never, never>,
68
+ ): Layer.Layer<ToolExecutionService, never, FunctionInvocationService> => {
39
69
  return Layer.effect(
40
70
  ToolExecutionService,
41
71
  Effect.gen(function* () {
42
72
  const toolkitHandler = yield* toolkit.pipe(Effect.provide(handlersLayer));
43
- const localFunctionExecutionService = yield* LocalFunctionExecutionService;
73
+ invariant(isHandlerLike(toolkitHandler));
74
+ const functionInvocationService = yield* FunctionInvocationService;
44
75
 
45
76
  return {
46
77
  handlersFor: (toolkit) => {
47
- const makeHandler = (tool: AiTool.Any): ((params: unknown) => Effect.Effect<unknown, any, any>) => {
48
- return Effect.fn('toolFunctionHandler')(function* (input: any) {
49
- if (toolkitHandler.tools.find((t: AiTool.Any) => t.name === tool.name)) {
78
+ const makeHandler = (tool: Tool.Any): ((params: unknown) => Effect.Effect<unknown, any, any>) => {
79
+ return Effect.fn(`toolFunctionHandler ${tool.name}`)(function* (input: any) {
80
+ if (toolkitHandler.tools[tool.name]) {
81
+ if (Tool.isProviderDefined(tool)) {
82
+ throw new Error('Attempted to call a provider-defined tool');
83
+ }
50
84
  // TODO(wittjosiah): Everything is `never` here.
51
85
  return yield* (toolkitHandler.handle as any)(tool.name, input);
52
86
  }
53
87
 
54
- const { functionName } = Context.get(FunctionToolAnnotation)(tool.annotations as any);
55
- const functionDef = functions.find((fn) => fn.name === functionName);
56
- if (!functionDef) {
57
- return yield* Effect.fail(new AiToolNotFoundError(tool.name));
58
- }
88
+ const { definition: functionDef } = Context.get(FunctionToolAnnotation)(tool.annotations as any);
59
89
 
60
- return yield* localFunctionExecutionService
61
- .invokeFunction(functionDef, input)
90
+ return yield* functionInvocationService
91
+ .invokeFunction(functionDef, input as any)
62
92
  .pipe(Effect.catchAllDefect((defect) => Effect.fail(defect)));
63
93
  });
64
94
  };
65
95
 
66
- return toolkit.of(Record.map(toolkit.tools, (tool, _name) => makeHandler(tool)) as any) as any;
96
+ return toolkit.of(
97
+ Record.map(toolkit.tools, (tool, _name) => (Tool.isUserDefined(tool) ? makeHandler(tool) : null)) as any,
98
+ ) as any;
67
99
  },
68
100
  };
69
101
  }),
70
102
  );
71
103
  };
72
104
 
73
- /**
74
- * Tools that are projected from functions have this annotation.
75
- */
76
- class FunctionToolAnnotation extends Context.Tag('@dxos/assisatnt/FunctionToolAnnotation')<
105
+ class FunctionToolAnnotation extends Context.Tag('@dxos/assistant/FunctionToolAnnotation')<
77
106
  FunctionToolAnnotation,
78
- {
79
- functionName: string;
80
- }
107
+ { definition: FunctionDefinition<any, any> }
81
108
  >() {}
82
109
 
83
- const toolCache = new WeakMap<FunctionDefinition<any, any>, AiTool.Any>();
110
+ const toolCache = new WeakMap<FunctionDefinition<any, any>, Tool.Any>();
84
111
 
85
- const projectFunctionToTool = (fn: FunctionDefinition<any, any>): AiTool.Any => {
112
+ /**
113
+ * Projects a `FunctionDefinition` into an `AiTool`.
114
+ *
115
+ * @param fn The function definition to project into a tool.
116
+ * @param meta Optional projection metadata.
117
+ * @param meta.deployedFunctionId Backend deployment ID used for remote invocation when present. This is the
118
+ * EDGE service's function deployment identifier (not the ECHO object ID/DXN and not `FunctionDefinition.key`).
119
+ */
120
+ const projectFunctionToTool = (fn: FunctionDefinition<any, any>): Tool.Any => {
86
121
  if (toolCache.has(fn)) {
87
122
  return toolCache.get(fn)!;
88
123
  }
89
124
 
90
- const tool = AiTool.make(makeToolName(fn.name), {
125
+ const tool = Tool.make(makeToolName(fn.name), {
91
126
  description: fn.description,
92
127
  parameters: createStructFieldsFromSchema(fn.inputSchema),
93
128
  // TODO(dmaretskyi): Include output schema.
94
129
  failure: Schema.Any, // TODO(dmaretskyi): Better type for the failure?
95
- }).annotate(FunctionToolAnnotation, { functionName: fn.name });
130
+ }).annotate(FunctionToolAnnotation, { definition: fn });
96
131
  toolCache.set(fn, tool);
97
132
  return tool;
98
133
  };
@@ -117,3 +152,7 @@ const createStructFieldsFromSchema = (schema: Schema.Schema<any, any>): Record<s
117
152
  return todo(`Unsupported schema AST: ${schema.ast._tag}`);
118
153
  }
119
154
  };
155
+
156
+ const isHandlerLike = (value: unknown): value is Toolkit.WithHandler<Record<string, Tool.Any>> => {
157
+ return typeof (value as any).tools === 'object' && typeof (value as any).handle === 'function';
158
+ };
package/src/index.ts CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  export * from './conversation';
6
6
  export * from './diff';
7
+ export * from './errors';
8
+ export * from './execution-graph';
7
9
  export * from './functions';
8
10
  export * from './session';
9
11
  export * from './templates';
10
12
  export * from './types';
11
- export * from './errors';
@@ -2,10 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Context } from 'effect';
5
+ import * as Context from 'effect/Context';
6
6
 
7
+ import { type ObjectId } from '@dxos/echo/internal';
7
8
  import { type ObjectVersion } from '@dxos/echo-db';
8
- import { type ObjectId } from '@dxos/echo-schema';
9
9
 
10
10
  /**
11
11
  * Resolves artifact ids to their versions.
@@ -2,9 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { AiError } from '@effect/ai';
6
- import { HttpClientError } from '@effect/platform';
7
- import { Effect, Schema } from 'effect';
5
+ import * as AiError from '@effect/ai/AiError';
6
+ import * as HttpClientError from '@effect/platform/HttpClientError';
7
+ import * as Effect from 'effect/Effect';
8
+ import * as Schema from 'effect/Schema';
8
9
 
9
10
  import { AiModelError } from '../errors';
10
11
 
@@ -16,6 +17,7 @@ const AnthropicErrorResponse = Schema.Struct({
16
17
  }),
17
18
  });
18
19
 
20
+ // TODO(dmaretskyi): Needs rework
19
21
  export const mapAiError = (err: AiError.AiError): Effect.Effect<AiError.AiError> =>
20
22
  Effect.gen(function* () {
21
23
  const cause = err.cause;
@@ -28,7 +30,7 @@ export const mapAiError = (err: AiError.AiError): Effect.Effect<AiError.AiError>
28
30
  message: body.error.message,
29
31
  context: { model: 'anthropic', type: body.error.type },
30
32
  });
31
- return new AiError.AiError({
33
+ return AiError.UnknownError.make({
32
34
  description: body.error.message,
33
35
  module: err.module,
34
36
  method: err.method,
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { beforeAll, describe, it } from '@effect/vitest';
6
- import { Effect } from 'effect';
6
+ import * as Effect from 'effect/Effect';
7
7
 
8
8
  import { Blueprint, Template } from '@dxos/blueprints';
9
9
  import { Obj } from '@dxos/echo';
@@ -2,12 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Array, Effect, Option, pipe } from 'effect';
5
+ import * as Array from 'effect/Array';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Function from 'effect/Function';
8
+ import * as Option from 'effect/Option';
6
9
 
7
10
  import { Template } from '@dxos/blueprints';
8
11
  import { Obj } from '@dxos/echo';
12
+ import { type ObjectId } from '@dxos/echo/internal';
9
13
  import { ObjectVersion } from '@dxos/echo-db';
10
- import { type ObjectId } from '@dxos/echo-schema';
11
14
  import { DatabaseService } from '@dxos/functions';
12
15
  import { log } from '@dxos/log';
13
16
  import { type ContentBlock, DataType } from '@dxos/schema';
@@ -28,7 +31,7 @@ export const formatSystemPrompt = ({
28
31
  objects = [],
29
32
  }: Pick<AiSessionRunParams<any>, 'system' | 'blueprints' | 'objects'>) =>
30
33
  Effect.gen(function* () {
31
- const blueprintDefs = yield* pipe(
34
+ const blueprintDefs = yield* Function.pipe(
32
35
  blueprints,
33
36
  Effect.forEach((blueprint) => Effect.succeed(blueprint.instructions)),
34
37
  Effect.flatMap(Effect.forEach((template) => DatabaseService.loadOption(template.source))),
@@ -47,7 +50,7 @@ export const formatSystemPrompt = ({
47
50
  ),
48
51
  );
49
52
 
50
- const objectDefs = yield* pipe(
53
+ const objectDefs = yield* Function.pipe(
51
54
  objects,
52
55
  Effect.forEach((object) =>
53
56
  Effect.succeed(trim`
@@ -60,7 +63,7 @@ export const formatSystemPrompt = ({
60
63
  Effect.map((objects) => (objects.length > 0 ? ['## Context Objects', ...objects].join('\n\n') : undefined)),
61
64
  );
62
65
 
63
- return yield* pipe(
66
+ return yield* Function.pipe(
64
67
  Effect.succeed([system, blueprintDefs, objectDefs].filter((def): def is string => def !== undefined)),
65
68
  Effect.map((parts) => parts.join('\n\n')),
66
69
  );
@@ -2,7 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Effect, Function } from 'effect';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Function from 'effect/Function';
6
7
 
7
8
  import { type AiParser, type ConsolePrinter } from '@dxos/ai';
8
9
  import { type DataType } from '@dxos/schema';
@@ -0,0 +1 @@
1
+ {"conversations":[{"parameters":{"model":"@anthropic/claude-3-5-sonnet-20241022","stream":true,"tools":[]},"prompt":{"content":[{"role":"user","content":"Hello world!","options":{}}]},"response":[{"type":"response-metadata","id":"msg_01Bm496JGgDMgC9YwdYTFFHo","modelId":"claude-3-5-sonnet-20241022","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"Hi","metadata":{}},{"type":"text-delta","id":"0","delta":"!","metadata":{}},{"type":"text-delta","id":"0","delta":" How can","metadata":{}},{"type":"text-delta","id":"0","delta":" I help you today?","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":10,"outputTokens":12,"totalTokens":22},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":10,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"@anthropic/claude-3-5-sonnet-20241022","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is 10 + 30?","options":{}}]},"response":[{"type":"response-metadata","id":"msg_01UNTbjtTHEpdFgGunu2k8Br","modelId":"claude-3-5-sonnet-20241022","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"I","metadata":{}},{"type":"text-delta","id":"0","delta":"'ll help","metadata":{}},{"type":"text-delta","id":"0","delta":" you calculate","metadata":{}},{"type":"text-delta","id":"0","delta":" ","metadata":{}},{"type":"text-delta","id":"0","delta":"10 + 30","metadata":{}},{"type":"text-delta","id":"0","delta":" using the","metadata":{}},{"type":"text-delta","id":"0","delta":" Calculator tool.","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"tool-params-start","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","name":"Calculator","providerExecuted":false,"metadata":{}},{"type":"tool-params-delta","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","delta":"","metadata":{}},{"type":"tool-params-delta","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","delta":"{\"input\": \"","metadata":{}},{"type":"tool-params-delta","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","delta":"10 + 30","metadata":{}},{"type":"tool-params-delta","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","delta":"\"}","metadata":{}},{"type":"tool-params-end","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","metadata":{}},{"type":"tool-call","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","name":"Calculator","params":{"input":"10 + 30"},"providerExecuted":false,"metadata":{}},{"type":"finish","reason":"tool-calls","usage":{"inputTokens":394,"outputTokens":73,"totalTokens":467},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":394,"output_tokens":1,"server_tool_use":null,"service_tier":"standard"}}}}]},{"parameters":{"model":"@anthropic/claude-3-5-sonnet-20241022","stream":true,"tools":[{"name":"Calculator","description":"Basic calculator tool","inputSchema":{"type":"object","required":["input"],"properties":{"input":{"type":"string","description":"The calculation to perform."}},"additionalProperties":false}}]},"prompt":{"content":[{"role":"user","content":"What is 10 + 30?","options":{}},{"role":"assistant","content":[{"type":"text","text":"I'll help you calculate 10 + 30 using the Calculator tool.","options":{}},{"type":"tool-call","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","name":"Calculator","params":{"input":"10 + 30"},"providerExecuted":false,"options":{}}],"options":{}},{"role":"tool","content":[{"type":"tool-result","id":"toolu_01LTGUgfihCQtVchm1b37xHQ","name":"Calculator","result":{"result":40},"options":{}}],"options":{}}]},"response":[{"type":"response-metadata","id":"msg_01AKio3GheJGn38BhvAp33rs","modelId":"claude-3-5-sonnet-20241022","timestamp":"1970-01-01T00:00:00.000Z","metadata":{}},{"type":"text-start","id":"0","metadata":{}},{"type":"text-delta","id":"0","delta":"The result","metadata":{}},{"type":"text-delta","id":"0","delta":" of 10 +","metadata":{}},{"type":"text-delta","id":"0","delta":" 30 is ","metadata":{}},{"type":"text-delta","id":"0","delta":"40.","metadata":{}},{"type":"text-end","id":"0","metadata":{}},{"type":"finish","reason":"stop","usage":{"inputTokens":482,"outputTokens":18,"totalTokens":500},"metadata":{"anthropic":{"usage":{"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"input_tokens":482,"output_tokens":3,"server_tool_use":null,"service_tier":"standard"}}}}]}]}
@@ -2,13 +2,17 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { AiTool, AiToolkit } from '@effect/ai';
5
+ import * as Tool from '@effect/ai/Tool';
6
+ import * as Toolkit from '@effect/ai/Toolkit';
6
7
  import { describe, it } from '@effect/vitest';
7
- import { Effect, Layer, Schema } from 'effect';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Layer from 'effect/Layer';
10
+ import * as Schema from 'effect/Schema';
8
11
 
9
- import { AiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
12
+ import { AiService, MemoizedAiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
10
13
  import { AiServiceTestingPreset } from '@dxos/ai/testing';
11
14
  import { Obj, Type } from '@dxos/echo';
15
+ import { TestHelpers } from '@dxos/effect';
12
16
  import { TracingService } from '@dxos/functions';
13
17
  import { log } from '@dxos/log';
14
18
 
@@ -29,8 +33,8 @@ const CalendarEventSchema = Schema.Struct({
29
33
 
30
34
  type CalendarEvent = Schema.Schema.Type<typeof CalendarEventSchema>;
31
35
 
32
- class TestToolkit extends AiToolkit.make(
33
- AiTool.make('Calculator', {
36
+ class TestToolkit extends Toolkit.make(
37
+ Tool.make('Calculator', {
34
38
  description: 'Basic calculator tool',
35
39
  parameters: {
36
40
  input: Schema.String.annotations({
@@ -61,175 +65,52 @@ const toolkitLayer = TestToolkit.toLayer({
61
65
  }),
62
66
  });
63
67
 
64
- describe.runIf(process.env.DX_RUN_SLOW_TESTS)('AiSession', () => {
65
- it.effect('no tools', () =>
66
- Effect.gen(function* () {
67
- const session = new AiSession({ operationModel: 'configured' });
68
- const response = yield* session.run({
69
- prompt: 'Hello world!',
70
- history: [],
71
- });
72
- log.info('response', { response });
73
- }).pipe(
74
- Effect.provide(
75
- Layer.mergeAll(
76
- toolkitLayer,
77
- AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
78
- Layer.provideMerge(ToolResolverService.layerEmpty),
79
- Layer.provideMerge(ToolExecutionService.layerEmpty),
80
- Layer.provideMerge(AiServiceTestingPreset('direct')),
81
- Layer.provideMerge(TracingService.layerNoop),
82
- ),
83
- ),
84
- ),
85
- ),
86
- );
68
+ const TestLayer = Layer.mergeAll(
69
+ toolkitLayer,
70
+ AiService.model('@anthropic/claude-3-5-sonnet-20241022'),
71
+ TracingService.layerNoop,
72
+ ToolResolverService.layerEmpty,
73
+ ToolExecutionService.layerEmpty,
74
+ ).pipe(
75
+ //
76
+ Layer.provideMerge(MemoizedAiService.layerTest()),
77
+ Layer.provide(AiServiceTestingPreset('direct')),
78
+ );
87
79
 
88
- it.effect('calculator', () =>
89
- Effect.gen(function* () {
90
- const session = new AiSession({ operationModel: 'configured' });
91
- const toolkit = yield* TestToolkit;
92
- const response = yield* session.run({
93
- toolkit,
94
- prompt: 'What is 10 + 20?',
95
- history: [],
96
- });
97
- log.info('response', { response });
98
- }).pipe(
99
- Effect.provide(
100
- Layer.mergeAll(
101
- TracingService.layerNoop,
102
- AiService.model('@anthropic/claude-3-5-sonnet-20241022').pipe(
103
- Layer.provideMerge(AiServiceTestingPreset('direct')),
104
- ),
105
- ToolResolverService.layerEmpty,
106
- ToolExecutionService.layerEmpty,
107
- toolkitLayer,
108
- ),
109
- ),
80
+ describe('AiSession', () => {
81
+ it.effect(
82
+ 'no tools',
83
+ Effect.fnUntraced(
84
+ function* (_) {
85
+ const session = new AiSession({ operationModel: 'configured' });
86
+ const response = yield* session.run({
87
+ prompt: 'Hello world!',
88
+ history: [],
89
+ });
90
+ log.info('response', { response });
91
+ },
92
+ Effect.provide(TestLayer),
93
+ TestHelpers.provideTestContext,
110
94
  ),
111
95
  );
112
96
 
113
- // TODO(dmaretskyi): Revive test.
114
- /*
115
- it.skip('create calendar itinerary', { timeout: 60_000 }, async () => {
116
- // overrides: { model: 'llama3.1:8b' },
117
- const session = new AiSession({ operationModel: 'configured' });
118
- const objects = new Set<string>();
119
-
120
- // Define calendar artifact.
121
- const calendarArtifact = defineArtifact({
122
- id: 'artifact:dxos.org/plugin/calendar',
123
- name: 'Calendar',
124
- instructions: 'Use this to create and query calendar events.',
125
- schema: CalendarEventSchema,
126
- tools: [
127
- createTool('calendar', {
128
- name: 'query',
129
- description: 'Query the calendar for events',
130
- schema: Schema.Struct({}),
131
- execute: async () => {
132
- return ToolResult.Success(CALENDAR_EVENTS);
133
- },
134
- }),
135
- ],
136
- });
137
-
138
- const tableArtifact = defineArtifact({
139
- id: 'artifact:dxos.org/plugin/table',
140
- name: 'Table',
141
- instructions: 'Use this to create and manage tables. Each table has a unique id.',
142
- schema: Schema.Struct({}),
143
- tools: [
144
- createTool('table', {
145
- name: 'create',
146
- description: 'Create a table',
147
- schema: Schema.Struct({
148
- data: Schema.Array(Schema.Any).annotations({ description: 'Array of data payloads to add as rows' }),
149
- }),
150
- execute: async ({ data }) => {
151
- log('create table', { data });
152
- const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
153
- objects.add(id);
154
- // TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
155
- return ToolResult.Success(`table @${id}`);
156
- },
157
- }),
158
- ],
159
- });
160
-
161
- const mapArtifact = defineArtifact({
162
- id: 'artifact:dxos.org/plugin/map',
163
- name: 'Map',
164
- instructions:
165
- 'Use this to create and manage maps. Maps source data from tables. Table id is required to create a map.',
166
- schema: Schema.Struct({}),
167
- tools: [
168
- createTool('map', {
169
- name: 'create',
170
- description: 'Create a map',
171
- schema: Schema.Struct({
172
- source: ArtifactId.annotations({
173
- description: 'The table that will be used as the source of the map',
174
- }),
175
- }),
176
- execute: async ({ source }) => {
177
- // TODO(dmaretskyi): Use effect-schema decode instead of manual parsing.
178
- const sourceId = ArtifactId.toDXN(source);
179
- if (!objects.has(sourceId.toString())) {
180
- return ToolResult.Error(`table id=${source} not found`);
181
- }
182
- log('create map', { sourceId });
183
- const id = DXN.fromLocalObjectId(ObjectId.random()).toString();
184
- objects.add(id);
185
- // TODO(dmaretskyi): consider xml for refs instead of @dxn:echo:@:XXXXX
186
- return ToolResult.Success(`map @${id}`);
187
- },
188
- }),
189
- ],
190
- });
191
-
192
- const scriptArtifact = defineArtifact({
193
- id: 'artifact:dxos.org/plugin/script',
194
- name: 'Script',
195
- instructions: 'Use this to create and manage scripts',
196
- schema: Schema.Struct({}),
197
- tools: [],
198
- });
199
-
200
- // session.streamEvent.on((event) => {
201
- // printStreamEvent(event);
202
- // });
203
-
204
- const printer = new ConsolePrinter();
205
- session.message.on((message) => printer.printMessage(message));
206
- session.userMessage.on((message) => printer.printMessage(message));
207
- session.block.on((block) => printer.printContentBlock(block));
208
-
209
- // session.update.on((update) => {
210
- // log('update', { update });
211
- // });
212
-
213
- // Test creating an itinerary
214
- const response = await session.run({
215
- client: aiClient,
216
- tools: [],
217
- artifacts: [calendarArtifact, tableArtifact, mapArtifact, scriptArtifact],
218
- requiredArtifactIds: [calendarArtifact.id, tableArtifact.id, mapArtifact.id, scriptArtifact.id],
219
- history: [],
220
- generationOptions: {
221
- model: '@anthropic/claude-3-5-haiku-20241022',
97
+ it.effect(
98
+ 'calculator',
99
+ Effect.fnUntraced(
100
+ function* (_) {
101
+ const session = new AiSession({ operationModel: 'configured' });
102
+ const toolkit = yield* TestToolkit;
103
+ const response = yield* session.run({
104
+ toolkit,
105
+ prompt: 'What is 10 + 30?',
106
+ history: [],
107
+ });
108
+ log.info('response', { response });
222
109
  },
223
- prompt: 'create a table and map for a travel itinerary based on events in my calendar',
224
- toolResolver: new ToolRegistry([]),
225
- });
226
-
227
- log('result', {
228
- objects,
229
- finalMessage: response.at(-1),
230
- });
231
- });
232
- */
110
+ Effect.provide(TestLayer),
111
+ TestHelpers.provideTestContext,
112
+ ),
113
+ );
233
114
  });
234
115
 
235
116
  // Travel to rome, florence, livorno, siena, madrid for conferences
@@ -266,43 +147,3 @@ const _CALENDAR_EVENTS: CalendarEvent[] = [
266
147
  description: 'Travel to Madrid',
267
148
  }),
268
149
  ];
269
-
270
- // const printStreamEvent = (event: GenerationStreamEvent) => {
271
- // switch (event.type) {
272
- // case 'message_start': {
273
- // process.stdout.write(`${event.message.role.toUpperCase()}\n\n`);
274
- // for (const content of event.message.content) {
275
- // printContentBlock(content);
276
- // }
277
- // break;
278
- // }
279
- // case 'content_block_start': {
280
- // printContentBlock(event.content);
281
- // break;
282
- // }
283
- // case 'content_block_delta': {
284
- // switch (event.delta.type) {
285
- // case 'text_delta': {
286
- // process.stdout.write(event.delta.text);
287
- // break;
288
- // }
289
- // case 'input_json_delta': {
290
- // process.stdout.write(event.delta.partial_json);
291
- // break;
292
- // }
293
- // }
294
- // break;
295
- // }
296
- // case 'content_block_stop': {
297
- // process.stdout.write('\n');
298
- // break;
299
- // }
300
- // case 'message_delta': {
301
- // break;
302
- // }
303
- // case 'message_stop': {
304
- // process.stdout.write('\n\n');
305
- // break;
306
- // }
307
- // }
308
- // };