@dxos/assistant-toolkit 0.8.4-main.2c6827d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/README.md +3 -0
- package/dist/lib/browser/index.mjs +2778 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/node-esm/index.mjs +2779 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/blueprints/design/design-blueprint.d.ts +4 -0
- package/dist/types/src/blueprints/design/design-blueprint.d.ts.map +1 -0
- package/dist/types/src/blueprints/design/design-blueprint.test.d.ts +2 -0
- package/dist/types/src/blueprints/design/design-blueprint.test.d.ts.map +1 -0
- package/dist/types/src/blueprints/design/index.d.ts +3 -0
- package/dist/types/src/blueprints/design/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/discord/discord-blueprint.d.ts +18 -0
- package/dist/types/src/blueprints/discord/discord-blueprint.d.ts.map +1 -0
- package/dist/types/src/blueprints/discord/index.d.ts +3 -0
- package/dist/types/src/blueprints/discord/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/index.d.ts +7 -0
- package/dist/types/src/blueprints/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/linear/index.d.ts +3 -0
- package/dist/types/src/blueprints/linear/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/linear/linear-blueprint.d.ts +18 -0
- package/dist/types/src/blueprints/linear/linear-blueprint.d.ts.map +1 -0
- package/dist/types/src/blueprints/planning/index.d.ts +3 -0
- package/dist/types/src/blueprints/planning/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/planning/planning-blueprint.d.ts +4 -0
- package/dist/types/src/blueprints/planning/planning-blueprint.d.ts.map +1 -0
- package/dist/types/src/blueprints/planning/planning-blueprint.test.d.ts +2 -0
- package/dist/types/src/blueprints/planning/planning-blueprint.test.d.ts.map +1 -0
- package/dist/types/src/blueprints/research/index.d.ts +3 -0
- package/dist/types/src/blueprints/research/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/research/research-blueprint.d.ts +4 -0
- package/dist/types/src/blueprints/research/research-blueprint.d.ts.map +1 -0
- package/dist/types/src/blueprints/research/research-blueprint.test.d.ts +2 -0
- package/dist/types/src/blueprints/research/research-blueprint.test.d.ts.map +1 -0
- package/dist/types/src/blueprints/testing.d.ts +12 -0
- package/dist/types/src/blueprints/testing.d.ts.map +1 -0
- package/dist/types/src/blueprints/websearch/index.d.ts +4 -0
- package/dist/types/src/blueprints/websearch/index.d.ts.map +1 -0
- package/dist/types/src/blueprints/websearch/websearch-blueprint.d.ts +4 -0
- package/dist/types/src/blueprints/websearch/websearch-blueprint.d.ts.map +1 -0
- package/dist/types/src/blueprints/websearch/websearch-toolkit.d.ts +27 -0
- package/dist/types/src/blueprints/websearch/websearch-toolkit.d.ts.map +1 -0
- package/dist/types/src/crud/graph.d.ts +64 -0
- package/dist/types/src/crud/graph.d.ts.map +1 -0
- package/dist/types/src/crud/graph.test.d.ts +2 -0
- package/dist/types/src/crud/graph.test.d.ts.map +1 -0
- package/dist/types/src/crud/index.d.ts +2 -0
- package/dist/types/src/crud/index.d.ts.map +1 -0
- package/dist/types/src/experimental/feed.test.d.ts +2 -0
- package/dist/types/src/experimental/feed.test.d.ts.map +1 -0
- package/dist/types/src/functions/agent/index.d.ts +5 -0
- package/dist/types/src/functions/agent/index.d.ts.map +1 -0
- package/dist/types/src/functions/agent/prompt.d.ts +9 -0
- package/dist/types/src/functions/agent/prompt.d.ts.map +1 -0
- package/dist/types/src/functions/discord/fetch-messages.d.ts +11 -0
- package/dist/types/src/functions/discord/fetch-messages.d.ts.map +1 -0
- package/dist/types/src/functions/discord/fetch-messages.test.d.ts +2 -0
- package/dist/types/src/functions/discord/fetch-messages.test.d.ts.map +1 -0
- package/dist/types/src/functions/discord/index.d.ts +12 -0
- package/dist/types/src/functions/discord/index.d.ts.map +1 -0
- package/dist/types/src/functions/document/index.d.ts +13 -0
- package/dist/types/src/functions/document/index.d.ts.map +1 -0
- package/dist/types/src/functions/document/read.d.ts +7 -0
- package/dist/types/src/functions/document/read.d.ts.map +1 -0
- package/dist/types/src/functions/document/update.d.ts +6 -0
- package/dist/types/src/functions/document/update.d.ts.map +1 -0
- package/dist/types/src/functions/entity-extraction/entity-extraction.d.ts +173 -0
- package/dist/types/src/functions/entity-extraction/entity-extraction.d.ts.map +1 -0
- package/dist/types/src/functions/entity-extraction/entity-extraction.test.d.ts +2 -0
- package/dist/types/src/functions/entity-extraction/entity-extraction.test.d.ts.map +1 -0
- package/dist/types/src/functions/entity-extraction/index.d.ts +174 -0
- package/dist/types/src/functions/entity-extraction/index.d.ts.map +1 -0
- package/dist/types/src/functions/exa/exa.d.ts +5 -0
- package/dist/types/src/functions/exa/exa.d.ts.map +1 -0
- package/dist/types/src/functions/exa/index.d.ts +3 -0
- package/dist/types/src/functions/exa/index.d.ts.map +1 -0
- package/dist/types/src/functions/exa/mock.d.ts +5 -0
- package/dist/types/src/functions/exa/mock.d.ts.map +1 -0
- package/dist/types/src/functions/github/fetch-prs.d.ts +6 -0
- package/dist/types/src/functions/github/fetch-prs.d.ts.map +1 -0
- package/dist/types/src/functions/index.d.ts +8 -0
- package/dist/types/src/functions/index.d.ts.map +1 -0
- package/dist/types/src/functions/linear/index.d.ts +9 -0
- package/dist/types/src/functions/linear/index.d.ts.map +1 -0
- package/dist/types/src/functions/linear/linear.test.d.ts +2 -0
- package/dist/types/src/functions/linear/linear.test.d.ts.map +1 -0
- package/dist/types/src/functions/linear/sync-issues.d.ts +12 -0
- package/dist/types/src/functions/linear/sync-issues.d.ts.map +1 -0
- package/dist/types/src/functions/research/document-create.d.ts +9 -0
- package/dist/types/src/functions/research/document-create.d.ts.map +1 -0
- package/dist/types/src/functions/research/index.d.ts +21 -0
- package/dist/types/src/functions/research/index.d.ts.map +1 -0
- package/dist/types/src/functions/research/research-graph.d.ts +18 -0
- package/dist/types/src/functions/research/research-graph.d.ts.map +1 -0
- package/dist/types/src/functions/research/research.d.ts +14 -0
- package/dist/types/src/functions/research/research.d.ts.map +1 -0
- package/dist/types/src/functions/research/research.test.d.ts +2 -0
- package/dist/types/src/functions/research/research.test.d.ts.map +1 -0
- package/dist/types/src/functions/research/types.d.ts +6 -0
- package/dist/types/src/functions/research/types.d.ts.map +1 -0
- package/dist/types/src/functions/tasks/index.d.ts +15 -0
- package/dist/types/src/functions/tasks/index.d.ts.map +1 -0
- package/dist/types/src/functions/tasks/read.d.ts +7 -0
- package/dist/types/src/functions/tasks/read.d.ts.map +1 -0
- package/dist/types/src/functions/tasks/task-list.d.ts +74 -0
- package/dist/types/src/functions/tasks/task-list.d.ts.map +1 -0
- package/dist/types/src/functions/tasks/task-list.test.d.ts +2 -0
- package/dist/types/src/functions/tasks/task-list.test.d.ts.map +1 -0
- package/dist/types/src/functions/tasks/update.d.ts +9 -0
- package/dist/types/src/functions/tasks/update.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +7 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/plugins.d.ts +19 -0
- package/dist/types/src/plugins.d.ts.map +1 -0
- package/dist/types/src/sync/index.d.ts +2 -0
- package/dist/types/src/sync/index.d.ts.map +1 -0
- package/dist/types/src/sync/sync.d.ts +15 -0
- package/dist/types/src/sync/sync.d.ts.map +1 -0
- package/dist/types/src/testing/data/exa-search-1748337321991.d.ts +38 -0
- package/dist/types/src/testing/data/exa-search-1748337321991.d.ts.map +1 -0
- package/dist/types/src/testing/data/exa-search-1748337331526.d.ts +37 -0
- package/dist/types/src/testing/data/exa-search-1748337331526.d.ts.map +1 -0
- package/dist/types/src/testing/data/exa-search-1748337344119.d.ts +58 -0
- package/dist/types/src/testing/data/exa-search-1748337344119.d.ts.map +1 -0
- package/dist/types/src/testing/data/index.d.ts +3 -0
- package/dist/types/src/testing/data/index.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/toolkits/AssistantToolkit.d.ts +17 -0
- package/dist/types/src/toolkits/AssistantToolkit.d.ts.map +1 -0
- package/dist/types/src/toolkits/AssistantToolkit.test.d.ts +2 -0
- package/dist/types/src/toolkits/AssistantToolkit.test.d.ts.map +1 -0
- package/dist/types/src/toolkits/SystemToolkit.d.ts +67 -0
- package/dist/types/src/toolkits/SystemToolkit.d.ts.map +1 -0
- package/dist/types/src/toolkits/index.d.ts +3 -0
- package/dist/types/src/toolkits/index.d.ts.map +1 -0
- package/dist/types/src/util/graphql.d.ts +22 -0
- package/dist/types/src/util/graphql.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +69 -0
- package/src/blueprints/design/design-blueprint.test.ts +103 -0
- package/src/blueprints/design/design-blueprint.ts +33 -0
- package/src/blueprints/design/index.ts +7 -0
- package/src/blueprints/discord/discord-blueprint.ts +34 -0
- package/src/blueprints/discord/index.ts +7 -0
- package/src/blueprints/index.ts +10 -0
- package/src/blueprints/linear/index.ts +7 -0
- package/src/blueprints/linear/linear-blueprint.ts +35 -0
- package/src/blueprints/planning/index.ts +7 -0
- package/src/blueprints/planning/planning-blueprint.test.ts +124 -0
- package/src/blueprints/planning/planning-blueprint.ts +98 -0
- package/src/blueprints/research/index.ts +7 -0
- package/src/blueprints/research/research-blueprint.test.ts +7 -0
- package/src/blueprints/research/research-blueprint.ts +53 -0
- package/src/blueprints/testing.ts +34 -0
- package/src/blueprints/websearch/index.ts +8 -0
- package/src/blueprints/websearch/websearch-blueprint.ts +20 -0
- package/src/blueprints/websearch/websearch-toolkit.ts +8 -0
- package/src/crud/graph.test.ts +69 -0
- package/src/crud/graph.ts +388 -0
- package/src/crud/index.ts +5 -0
- package/src/experimental/feed.test.ts +111 -0
- package/src/functions/agent/index.ts +11 -0
- package/src/functions/agent/prompt.ts +114 -0
- package/src/functions/discord/fetch-messages.test.ts +58 -0
- package/src/functions/discord/fetch-messages.ts +252 -0
- package/src/functions/discord/index.ts +9 -0
- package/src/functions/document/index.ts +12 -0
- package/src/functions/document/read.ts +29 -0
- package/src/functions/document/update.ts +30 -0
- package/src/functions/entity-extraction/entity-extraction.conversations.json +1 -0
- package/src/functions/entity-extraction/entity-extraction.test.ts +92 -0
- package/src/functions/entity-extraction/entity-extraction.ts +164 -0
- package/src/functions/entity-extraction/index.ts +9 -0
- package/src/functions/exa/exa.ts +37 -0
- package/src/functions/exa/index.ts +6 -0
- package/src/functions/exa/mock.ts +71 -0
- package/src/functions/github/fetch-prs.ts +30 -0
- package/src/functions/index.ts +11 -0
- package/src/functions/linear/index.ts +9 -0
- package/src/functions/linear/linear.test.ts +83 -0
- package/src/functions/linear/sync-issues.ts +189 -0
- package/src/functions/research/document-create.ts +75 -0
- package/src/functions/research/index.ts +14 -0
- package/src/functions/research/research-graph.ts +47 -0
- package/src/functions/research/research-instructions.tpl +106 -0
- package/src/functions/research/research.conversations.json +1 -0
- package/src/functions/research/research.test.ts +179 -0
- package/src/functions/research/research.ts +190 -0
- package/src/functions/research/types.ts +26 -0
- package/src/functions/tasks/index.ts +11 -0
- package/src/functions/tasks/read.ts +34 -0
- package/src/functions/tasks/task-list.test.ts +99 -0
- package/src/functions/tasks/task-list.ts +165 -0
- package/src/functions/tasks/update.ts +52 -0
- package/src/index.ts +10 -0
- package/src/plugins.tsx +68 -0
- package/src/sync/index.ts +5 -0
- package/src/sync/sync.ts +87 -0
- package/src/testing/data/exa-search-1748337321991.ts +131 -0
- package/src/testing/data/exa-search-1748337331526.ts +144 -0
- package/src/testing/data/exa-search-1748337344119.ts +133 -0
- package/src/testing/data/index.ts +11 -0
- package/src/testing/index.ts +5 -0
- package/src/toolkits/AssistantToolkit.conversations.json +1 -0
- package/src/toolkits/AssistantToolkit.test.ts +88 -0
- package/src/toolkits/AssistantToolkit.ts +47 -0
- package/src/toolkits/SystemToolkit.ts +231 -0
- package/src/toolkits/index.ts +6 -0
- package/src/typedefs.d.ts +8 -0
- package/src/util/graphql.ts +31 -0
- package/src/util/index.ts +5 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { ToolId } from '@dxos/ai';
|
|
6
|
+
import { Blueprint } from '@dxos/blueprints';
|
|
7
|
+
import { Obj, Ref } from '@dxos/echo';
|
|
8
|
+
import { Text } from '@dxos/schema';
|
|
9
|
+
|
|
10
|
+
const blueprint: Blueprint.Blueprint = Obj.make(Blueprint.Blueprint, {
|
|
11
|
+
key: 'dxos.org/blueprint/web-search',
|
|
12
|
+
name: 'Web Search',
|
|
13
|
+
description: 'Search the web.',
|
|
14
|
+
instructions: {
|
|
15
|
+
source: Ref.make(Text.make()),
|
|
16
|
+
},
|
|
17
|
+
tools: [ToolId.make('AnthropicWebSearch')],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default blueprint;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as AnthropicClient from '@effect/ai-anthropic/AnthropicClient';
|
|
6
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
7
|
+
import { describe, it } from '@effect/vitest';
|
|
8
|
+
import * as Config from 'effect/Config';
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import * as Function from 'effect/Function';
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
12
|
+
|
|
13
|
+
import { AiService, ToolExecutionService, ToolResolverService } from '@dxos/ai';
|
|
14
|
+
import * as AiServiceRouter from '@dxos/ai/AiServiceRouter';
|
|
15
|
+
import { tapHttpErrors } from '@dxos/ai/testing';
|
|
16
|
+
import { AiSession } from '@dxos/assistant';
|
|
17
|
+
import { TestHelpers } from '@dxos/effect';
|
|
18
|
+
import { DatabaseService, TracingService } from '@dxos/functions';
|
|
19
|
+
import { log } from '@dxos/log';
|
|
20
|
+
import { Project } from '@dxos/types';
|
|
21
|
+
|
|
22
|
+
import { makeGraphWriterHandler, makeGraphWriterToolkit } from './graph';
|
|
23
|
+
|
|
24
|
+
// import { type EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
25
|
+
|
|
26
|
+
const TestLayer = Function.pipe(
|
|
27
|
+
AiService.model('@anthropic/claude-3-5-sonnet-20241022'),
|
|
28
|
+
Layer.provideMerge(DatabaseService.notAvailable),
|
|
29
|
+
Layer.provideMerge(ToolResolverService.layerEmpty),
|
|
30
|
+
Layer.provideMerge(ToolExecutionService.layerEmpty),
|
|
31
|
+
Layer.provide(AiServiceRouter.AiServiceRouter),
|
|
32
|
+
Layer.provide(
|
|
33
|
+
AnthropicClient.layerConfig({
|
|
34
|
+
apiKey: Config.redacted('ANTHROPIC_API_KEY'),
|
|
35
|
+
transformClient: tapHttpErrors,
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
Layer.provide(FetchHttpClient.layer),
|
|
39
|
+
Layer.provideMerge(TracingService.layerNoop),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
describe('graph', () => {
|
|
43
|
+
// let builder: EchoTestBuilder;
|
|
44
|
+
// test('findRelatedSchema', async () => {
|
|
45
|
+
// const db = await createEchoDatabase();
|
|
46
|
+
// const relatedSchemas = await findRelatedSchema(db, Schema.Struct({}));
|
|
47
|
+
// });
|
|
48
|
+
|
|
49
|
+
const Toolkit = makeGraphWriterToolkit({ schema: [Project.Project] });
|
|
50
|
+
const ToolkitLayer = makeGraphWriterHandler(Toolkit);
|
|
51
|
+
|
|
52
|
+
it.effect.skip(
|
|
53
|
+
'calculator',
|
|
54
|
+
Effect.fn(
|
|
55
|
+
function* (_) {
|
|
56
|
+
const session = new AiSession();
|
|
57
|
+
const toolkit = yield* Toolkit;
|
|
58
|
+
const response = yield* session.run({
|
|
59
|
+
toolkit,
|
|
60
|
+
prompt: 'What is 10 + 20?',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
log.info('response', { response });
|
|
64
|
+
},
|
|
65
|
+
Effect.provide(Layer.mergeAll(TestLayer, ToolkitLayer)),
|
|
66
|
+
TestHelpers.runIf(process.env.ANTHROPIC_API_KEY),
|
|
67
|
+
),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Tool from '@effect/ai/Tool';
|
|
6
|
+
import * as Toolkit from '@effect/ai/Toolkit';
|
|
7
|
+
import * as Context from 'effect/Context';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Function from 'effect/Function';
|
|
10
|
+
import * as Option from 'effect/Option';
|
|
11
|
+
import * as Schema from 'effect/Schema';
|
|
12
|
+
import * as SchemaAST from 'effect/SchemaAST';
|
|
13
|
+
|
|
14
|
+
import { Obj, type Relation } from '@dxos/echo';
|
|
15
|
+
import { Filter, Query } from '@dxos/echo';
|
|
16
|
+
import {
|
|
17
|
+
EntityKind,
|
|
18
|
+
ObjectId,
|
|
19
|
+
ReferenceAnnotationId,
|
|
20
|
+
RelationSourceDXNId,
|
|
21
|
+
RelationSourceId,
|
|
22
|
+
RelationTargetDXNId,
|
|
23
|
+
RelationTargetId,
|
|
24
|
+
create,
|
|
25
|
+
getEntityKind,
|
|
26
|
+
getSchemaDXN,
|
|
27
|
+
getSchemaTypename,
|
|
28
|
+
getTypeAnnotation,
|
|
29
|
+
getTypeIdentifierAnnotation,
|
|
30
|
+
} from '@dxos/echo/internal';
|
|
31
|
+
import { type EchoDatabase, type Queue } from '@dxos/echo-db';
|
|
32
|
+
import { isEncodedReference } from '@dxos/echo-protocol';
|
|
33
|
+
import { mapAst } from '@dxos/effect';
|
|
34
|
+
import { ContextQueueService, DatabaseService } from '@dxos/functions';
|
|
35
|
+
import { DXN } from '@dxos/keys';
|
|
36
|
+
import { log } from '@dxos/log';
|
|
37
|
+
import { deepMapValues, isNonNullable, trim } from '@dxos/util';
|
|
38
|
+
|
|
39
|
+
// TODO(burdon): Unify with the graph schema.
|
|
40
|
+
export const Subgraph = Schema.Struct({
|
|
41
|
+
/** Objects and relations. */
|
|
42
|
+
objects: Schema.Array(Schema.Any),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export interface Subgraph extends Schema.Schema.Type<typeof Subgraph> {}
|
|
46
|
+
|
|
47
|
+
export type RelatedSchema = {
|
|
48
|
+
schema: Schema.Schema.AnyNoContext;
|
|
49
|
+
kind: 'reference' | 'relation';
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Find all schemas that are related to the given schema.
|
|
54
|
+
*
|
|
55
|
+
* @param db
|
|
56
|
+
* @param schema
|
|
57
|
+
* @returns
|
|
58
|
+
*/
|
|
59
|
+
export const findRelatedSchema = async (
|
|
60
|
+
db: EchoDatabase,
|
|
61
|
+
anchor: Schema.Schema.AnyNoContext,
|
|
62
|
+
): Promise<RelatedSchema[]> => {
|
|
63
|
+
// TODO(dmaretskyi): Query stored schemas.
|
|
64
|
+
const allSchemas = [...db.graph.schemaRegistry.schemas];
|
|
65
|
+
|
|
66
|
+
// TODO(dmaretskyi): Also do references.
|
|
67
|
+
return allSchemas
|
|
68
|
+
.filter((schema) => {
|
|
69
|
+
if (getTypeAnnotation(schema)?.kind !== EntityKind.Relation) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
isSchemaAddressableByDxn(anchor, DXN.parse(getTypeAnnotation(schema)!.sourceSchema!)) ||
|
|
75
|
+
isSchemaAddressableByDxn(anchor, DXN.parse(getTypeAnnotation(schema)!.targetSchema!))
|
|
76
|
+
);
|
|
77
|
+
})
|
|
78
|
+
.map(
|
|
79
|
+
(schema): RelatedSchema => ({
|
|
80
|
+
schema,
|
|
81
|
+
kind: 'relation',
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Non-strict DXN comparison.
|
|
88
|
+
* Returns true if the DXN could be resolved to the schema.
|
|
89
|
+
*/
|
|
90
|
+
const isSchemaAddressableByDxn = (schema: Schema.Schema.AnyNoContext, dxn: DXN): boolean => {
|
|
91
|
+
if (getTypeIdentifierAnnotation(schema) === dxn.toString()) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const t = dxn.asTypeDXN();
|
|
96
|
+
if (t) {
|
|
97
|
+
return t.type === getSchemaTypename(schema);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return false;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Perform vector search in the local database.
|
|
105
|
+
*/
|
|
106
|
+
// TODO(dmaretskyi): Rename `GraphReadToolkit`.
|
|
107
|
+
export const LocalSearchToolkit = Toolkit.make(
|
|
108
|
+
Tool.make('search_local_search', {
|
|
109
|
+
description: 'Search the local database for information using a vector index',
|
|
110
|
+
parameters: {
|
|
111
|
+
query: Schema.String.annotations({
|
|
112
|
+
description: 'The query to search for. Could be a question or a topic or a set of keywords.',
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
success: Schema.Unknown,
|
|
116
|
+
failure: Schema.Never,
|
|
117
|
+
dependencies: [DatabaseService],
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
export const LocalSearchHandler = LocalSearchToolkit.toLayer({
|
|
122
|
+
search_local_search: Effect.fn(function* ({ query }) {
|
|
123
|
+
const { objects } = yield* DatabaseService.runQuery(Query.select(Filter.text(query, { type: 'vector' })));
|
|
124
|
+
const results = [...objects];
|
|
125
|
+
|
|
126
|
+
const option = yield* Effect.serviceOption(ContextQueueService);
|
|
127
|
+
if (Option.isSome(option)) {
|
|
128
|
+
const queueObjects = yield* Effect.promise(() => option.value.queue.queryObjects());
|
|
129
|
+
// TODO(dmaretskyi): Text search on the queue.
|
|
130
|
+
results.push(...queueObjects);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return trim`
|
|
134
|
+
<local_context>
|
|
135
|
+
${JSON.stringify(results, null, 2)}
|
|
136
|
+
</local_context>
|
|
137
|
+
`;
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Attached as an annotation to the writer tool.
|
|
143
|
+
*/
|
|
144
|
+
class GraphWriterSchema extends Context.Tag('@dxos/assistant/GraphWriterSchema')<
|
|
145
|
+
GraphWriterSchema,
|
|
146
|
+
{
|
|
147
|
+
schema: Schema.Schema.AnyNoContext[];
|
|
148
|
+
}
|
|
149
|
+
>() {}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Forms typed objects that can be written to the graph database.
|
|
153
|
+
*/
|
|
154
|
+
export const makeGraphWriterToolkit = ({ schema }: { schema: Schema.Schema.AnyNoContext[] }) => {
|
|
155
|
+
return Toolkit.make(
|
|
156
|
+
Tool.make('graph_writer', {
|
|
157
|
+
description: 'Write to the local graph database',
|
|
158
|
+
parameters: createExtractionSchema(schema).fields,
|
|
159
|
+
success: Schema.Unknown,
|
|
160
|
+
failure: Schema.Never,
|
|
161
|
+
dependencies: [DatabaseService, ContextQueueService],
|
|
162
|
+
}).annotateContext(Context.make(GraphWriterSchema, { schema })),
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const makeGraphWriterHandler = (
|
|
167
|
+
toolkit: ReturnType<typeof makeGraphWriterToolkit>,
|
|
168
|
+
{
|
|
169
|
+
onAppend,
|
|
170
|
+
}: {
|
|
171
|
+
onAppend?: (object: DXN[]) => void;
|
|
172
|
+
} = {},
|
|
173
|
+
) => {
|
|
174
|
+
const { schema } = Context.get(
|
|
175
|
+
toolkit.tools.graph_writer.annotations as Context.Context<GraphWriterSchema>,
|
|
176
|
+
GraphWriterSchema,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return toolkit.toLayer({
|
|
180
|
+
graph_writer: Effect.fn(function* (input) {
|
|
181
|
+
const { db } = yield* DatabaseService;
|
|
182
|
+
const { queue } = yield* ContextQueueService;
|
|
183
|
+
const data = yield* Effect.promise(() => sanitizeObjects(schema, input as any, db, queue));
|
|
184
|
+
yield* Effect.promise(() => queue.append(data as Obj.Any[]));
|
|
185
|
+
|
|
186
|
+
const dxns = data.map((obj) => Obj.getDXN(obj));
|
|
187
|
+
onAppend?.(dxns);
|
|
188
|
+
return dxns;
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a schema for structured data extraction.
|
|
195
|
+
*/
|
|
196
|
+
export const createExtractionSchema = (types: Schema.Schema.AnyNoContext[]) => {
|
|
197
|
+
return Schema.Struct({
|
|
198
|
+
...Object.fromEntries(
|
|
199
|
+
types.map(preprocessSchema).map((schema, index) => [
|
|
200
|
+
`objects_${getSanitizedSchemaName(types[index])}`,
|
|
201
|
+
Schema.optional(Schema.Array(schema)).annotations({
|
|
202
|
+
description: `The objects of type: ${getSchemaDXN(types[index])?.asTypeDXN()!.type}. ${SchemaAST.getDescriptionAnnotation(types[index].ast).pipe(Option.getOrElse(() => ''))}`,
|
|
203
|
+
}),
|
|
204
|
+
]),
|
|
205
|
+
),
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const getSanitizedSchemaName = (schema: Schema.Schema.AnyNoContext) => {
|
|
210
|
+
return getSchemaDXN(schema)!
|
|
211
|
+
.asTypeDXN()!
|
|
212
|
+
.type.replaceAll(/[^a-zA-Z0-9]+/g, '_');
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
export const sanitizeObjects = async (
|
|
216
|
+
types: Schema.Schema.AnyNoContext[],
|
|
217
|
+
data: Record<string, readonly unknown[]>,
|
|
218
|
+
db: EchoDatabase,
|
|
219
|
+
queue?: Queue,
|
|
220
|
+
): Promise<Obj.Any[]> => {
|
|
221
|
+
const entries = types
|
|
222
|
+
.map(
|
|
223
|
+
(type) =>
|
|
224
|
+
data[`objects_${getSanitizedSchemaName(type)}`]?.map((object: any) => ({
|
|
225
|
+
data: object,
|
|
226
|
+
schema: type,
|
|
227
|
+
})) ?? [],
|
|
228
|
+
)
|
|
229
|
+
.flat();
|
|
230
|
+
|
|
231
|
+
const idMap = new Map<string, string>();
|
|
232
|
+
const existingIds = new Set<ObjectId>();
|
|
233
|
+
const enitties = new Map<ObjectId, Obj.Any | Relation.Any>();
|
|
234
|
+
|
|
235
|
+
const resolveId = (id: string): DXN | undefined => {
|
|
236
|
+
if (ObjectId.isValid(id)) {
|
|
237
|
+
existingIds.add(id);
|
|
238
|
+
return DXN.fromLocalObjectId(id);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const mappedId = idMap.get(id);
|
|
242
|
+
if (mappedId) {
|
|
243
|
+
return DXN.fromLocalObjectId(mappedId);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return undefined;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const res = entries
|
|
250
|
+
.map((entry) => {
|
|
251
|
+
// This entry mutates existing object.
|
|
252
|
+
if (ObjectId.isValid(entry.data.id)) {
|
|
253
|
+
return entry;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
idMap.set(entry.data.id, ObjectId.random());
|
|
257
|
+
entry.data.id = idMap.get(entry.data.id);
|
|
258
|
+
return entry;
|
|
259
|
+
})
|
|
260
|
+
.map((entry) => {
|
|
261
|
+
const data = deepMapValues(entry.data, (value, recurse) => {
|
|
262
|
+
if (isEncodedReference(value)) {
|
|
263
|
+
const ref = value['/'];
|
|
264
|
+
const id = resolveId(ref);
|
|
265
|
+
|
|
266
|
+
if (id) {
|
|
267
|
+
// Link to an existing object.
|
|
268
|
+
return { '/': id.toString() };
|
|
269
|
+
} else {
|
|
270
|
+
// Search URIs?
|
|
271
|
+
return { '/': `search:?q=${encodeURIComponent(ref)}` };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return recurse(value);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (getEntityKind(entry.schema) === 'relation') {
|
|
279
|
+
const sourceDxn = resolveId(data.source);
|
|
280
|
+
if (!sourceDxn) {
|
|
281
|
+
log.warn('source not found', { source: data.source });
|
|
282
|
+
}
|
|
283
|
+
const targetDxn = resolveId(data.target);
|
|
284
|
+
if (!targetDxn) {
|
|
285
|
+
log.warn('target not found', { target: data.target });
|
|
286
|
+
}
|
|
287
|
+
delete data.source;
|
|
288
|
+
delete data.target;
|
|
289
|
+
data[RelationSourceDXNId] = sourceDxn;
|
|
290
|
+
data[RelationTargetDXNId] = targetDxn;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
data,
|
|
295
|
+
schema: entry.schema,
|
|
296
|
+
};
|
|
297
|
+
})
|
|
298
|
+
.filter((object) => !existingIds.has(object.data.id)); // TODO(dmaretskyi): This dissallows updating existing objects.
|
|
299
|
+
|
|
300
|
+
// TODO(dmaretskyi): Use ref resolver.
|
|
301
|
+
const { objects: dbObjects } = await db.query(Query.select(Filter.ids(...existingIds))).run();
|
|
302
|
+
const queueObjects = (await queue?.getObjectsById([...existingIds])) ?? [];
|
|
303
|
+
const objects = [...dbObjects, ...queueObjects].filter(isNonNullable);
|
|
304
|
+
|
|
305
|
+
// TODO(dmaretskyi): Returns everything if IDs are empty!
|
|
306
|
+
log.info('objects', { dbObjects, queueObjects, existingIds });
|
|
307
|
+
const missing = Array.from(existingIds).filter((id) => !objects.some((object) => object.id === id));
|
|
308
|
+
if (missing.length > 0) {
|
|
309
|
+
throw new Error(`Object IDs do not point to existing objects: ${missing.join(', ')}`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return res.flatMap(({ data, schema }) => {
|
|
313
|
+
let skip = false;
|
|
314
|
+
if (RelationSourceDXNId in data) {
|
|
315
|
+
const id = (data[RelationSourceDXNId] as DXN).asEchoDXN()?.echoId;
|
|
316
|
+
const obj = objects.find((object) => object.id === id) ?? enitties.get(id!);
|
|
317
|
+
if (obj) {
|
|
318
|
+
delete data[RelationSourceDXNId];
|
|
319
|
+
data[RelationSourceId] = obj;
|
|
320
|
+
} else {
|
|
321
|
+
skip = true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (RelationTargetDXNId in data) {
|
|
325
|
+
const id = (data[RelationTargetDXNId] as DXN).asEchoDXN()?.echoId;
|
|
326
|
+
const obj = objects.find((object) => object.id === id) ?? enitties.get(id!);
|
|
327
|
+
if (obj) {
|
|
328
|
+
delete data[RelationTargetDXNId];
|
|
329
|
+
data[RelationTargetId] = obj;
|
|
330
|
+
} else {
|
|
331
|
+
skip = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!skip) {
|
|
335
|
+
const obj = create(schema, data);
|
|
336
|
+
enitties.set(obj.id, obj);
|
|
337
|
+
return [obj];
|
|
338
|
+
}
|
|
339
|
+
return [];
|
|
340
|
+
});
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const SoftRef = Schema.Struct({
|
|
344
|
+
'/': Schema.String,
|
|
345
|
+
}).annotations({
|
|
346
|
+
description: 'Reference to another object.',
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const preprocessSchema = (schema: Schema.Schema.AnyNoContext) => {
|
|
350
|
+
const isRelationSchema = getEntityKind(schema) === 'relation';
|
|
351
|
+
|
|
352
|
+
const go = (ast: SchemaAST.AST, visited = new Set<SchemaAST.AST>()): SchemaAST.AST => {
|
|
353
|
+
if (visited.has(ast)) {
|
|
354
|
+
// Already visited this node, prevent infinite recursion.
|
|
355
|
+
return ast;
|
|
356
|
+
}
|
|
357
|
+
visited.add(ast);
|
|
358
|
+
|
|
359
|
+
if (SchemaAST.getAnnotation(ast, ReferenceAnnotationId).pipe(Option.isSome)) {
|
|
360
|
+
return SoftRef.ast;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return mapAst(ast, (child) => go(child, visited));
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
return Schema.make<any, any, never>(mapAst(schema.ast, (ast) => go(ast))).pipe(
|
|
367
|
+
Schema.omit('id'),
|
|
368
|
+
Schema.extend(
|
|
369
|
+
Schema.Struct({
|
|
370
|
+
id: Schema.String.annotations({
|
|
371
|
+
description: 'The id of this object. Come up with a unique id based on your judgement.',
|
|
372
|
+
}),
|
|
373
|
+
}),
|
|
374
|
+
),
|
|
375
|
+
isRelationSchema
|
|
376
|
+
? Schema.extend(
|
|
377
|
+
Schema.Struct({
|
|
378
|
+
source: Schema.String.annotations({
|
|
379
|
+
description: 'The id of the source object for this relation.',
|
|
380
|
+
}),
|
|
381
|
+
target: Schema.String.annotations({
|
|
382
|
+
description: 'The id of the target object for this relation.',
|
|
383
|
+
}),
|
|
384
|
+
}),
|
|
385
|
+
)
|
|
386
|
+
: Function.identity<Schema.Schema.AnyNoContext>,
|
|
387
|
+
);
|
|
388
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Toolkit from '@effect/ai/Toolkit';
|
|
6
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
7
|
+
import { describe, it } from '@effect/vitest';
|
|
8
|
+
import * as Config from 'effect/Config';
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import * as Layer from 'effect/Layer';
|
|
11
|
+
import * as Redacted from 'effect/Redacted';
|
|
12
|
+
|
|
13
|
+
import { AiService } from '@dxos/ai';
|
|
14
|
+
import { AiServiceTestingPreset, EXA_API_KEY } from '@dxos/ai/testing';
|
|
15
|
+
import { makeToolExecutionServiceFromFunctions, makeToolResolverFromFunctions } from '@dxos/assistant';
|
|
16
|
+
import { TestHelpers } from '@dxos/effect';
|
|
17
|
+
import { CredentialsService, FunctionInvocationService } from '@dxos/functions';
|
|
18
|
+
import { TracingServiceExt } from '@dxos/functions-runtime';
|
|
19
|
+
import {
|
|
20
|
+
FunctionInvocationServiceLayerTestMocked,
|
|
21
|
+
TestDatabaseLayer,
|
|
22
|
+
testStoragePath,
|
|
23
|
+
} from '@dxos/functions-runtime/testing';
|
|
24
|
+
|
|
25
|
+
import { Discord, Linear } from '../functions';
|
|
26
|
+
|
|
27
|
+
const TestLayer = Layer.mergeAll(
|
|
28
|
+
AiService.model('@anthropic/claude-opus-4-0'),
|
|
29
|
+
makeToolResolverFromFunctions([], Toolkit.make()),
|
|
30
|
+
makeToolExecutionServiceFromFunctions(Toolkit.make() as any, Layer.empty as any),
|
|
31
|
+
).pipe(
|
|
32
|
+
Layer.provideMerge(
|
|
33
|
+
Layer.mergeAll(
|
|
34
|
+
AiServiceTestingPreset('direct'),
|
|
35
|
+
TestDatabaseLayer({
|
|
36
|
+
indexing: { vector: true },
|
|
37
|
+
types: [],
|
|
38
|
+
storagePath: testStoragePath({ name: 'feed-test' }),
|
|
39
|
+
}),
|
|
40
|
+
CredentialsService.layerConfig([
|
|
41
|
+
{ service: 'exa.ai', apiKey: Config.succeed(Redacted.make(EXA_API_KEY)) },
|
|
42
|
+
{ service: 'discord.com', apiKey: Config.redacted('DISCORD_TOKEN') },
|
|
43
|
+
{ service: 'linear.app', apiKey: Config.redacted('LINEAR_API_KEY') },
|
|
44
|
+
]),
|
|
45
|
+
FunctionInvocationServiceLayerTestMocked({ functions: [Linear.sync, Discord.fetch] }).pipe(
|
|
46
|
+
Layer.provideMerge(TracingServiceExt.layerLogInfo()),
|
|
47
|
+
),
|
|
48
|
+
FetchHttpClient.layer,
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
describe('Feed', { timeout: 600_000 }, () => {
|
|
54
|
+
it.effect(
|
|
55
|
+
'fetch discord messages',
|
|
56
|
+
Effect.fnUntraced(
|
|
57
|
+
function* (_) {
|
|
58
|
+
// const messages = yield* LocalFunctionExecutionService.invokeFunction(fetchDiscordMessages, {
|
|
59
|
+
// serverId: '837138313172353095',
|
|
60
|
+
// // channelId: '1404487604761526423',
|
|
61
|
+
// after: Date.now() / 1000 - 128 * 3600,
|
|
62
|
+
// });
|
|
63
|
+
// for (const message of messages) {
|
|
64
|
+
// console.log(message.sender.name, message.blocks.find((block) => block._tag === 'text')?.text);
|
|
65
|
+
// }
|
|
66
|
+
// console.log(`Fetched ${messages.length} messages`);
|
|
67
|
+
|
|
68
|
+
// const result = yield* AiSession.run({
|
|
69
|
+
// history: [
|
|
70
|
+
// Obj.make(Message.Message, {
|
|
71
|
+
// created: new Date().toISOString(),
|
|
72
|
+
// sender: { role: 'user' },
|
|
73
|
+
// blocks: messages
|
|
74
|
+
// .map(
|
|
75
|
+
// (message) =>
|
|
76
|
+
// ({
|
|
77
|
+
// _tag: 'text',
|
|
78
|
+
// text: message.sender.name + ': ' + message.blocks.find((block) => block._tag === 'text')?.text,
|
|
79
|
+
// }) as const,
|
|
80
|
+
// )
|
|
81
|
+
// .filter((block) => block._tag === 'text' && block.text.trim().length > 0),
|
|
82
|
+
// }),
|
|
83
|
+
// ],
|
|
84
|
+
// prompt: 'Summarize the messages.',
|
|
85
|
+
// system: 'Summarize the messages.',
|
|
86
|
+
// }).pipe(Effect.provide(AiService.model('@anthropic/claude-3-5-haiku-latest')));
|
|
87
|
+
// console.log(result);
|
|
88
|
+
|
|
89
|
+
const linearIssues = yield* FunctionInvocationService.invokeFunction(Linear.sync, {
|
|
90
|
+
team: '1127c63a-6f77-4725-9229-50f6cd47321c',
|
|
91
|
+
});
|
|
92
|
+
console.log(linearIssues);
|
|
93
|
+
|
|
94
|
+
// const result = yield* AiSession.run({
|
|
95
|
+
// history: [
|
|
96
|
+
// Obj.make(Message.Message, {
|
|
97
|
+
// created: new Date().toISOString(),
|
|
98
|
+
// sender: { role: 'user' },
|
|
99
|
+
// blocks: [{ _tag: 'text', text: JSON.stringify(linearIssues) }],
|
|
100
|
+
// }),
|
|
101
|
+
// ],
|
|
102
|
+
// prompt: 'Summarize the whats new.',
|
|
103
|
+
// system: 'Summarize the whats new. Reference specific people by name.',
|
|
104
|
+
// }).pipe(Effect.provide(AiService.model('@anthropic/claude-sonnet-4-0')));
|
|
105
|
+
// console.log(result.at(-1)?.blocks.find((block) => block._tag === 'text')?.text);
|
|
106
|
+
},
|
|
107
|
+
Effect.provide(TestLayer),
|
|
108
|
+
TestHelpers.taggedTest('llm'),
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type FunctionDefinition } from '@dxos/functions';
|
|
6
|
+
|
|
7
|
+
import { default as prompt$ } from './prompt';
|
|
8
|
+
|
|
9
|
+
export namespace Agent {
|
|
10
|
+
export const prompt: FunctionDefinition.Any = prompt$; // TODO(burdon): Temp fix for TS error.
|
|
11
|
+
}
|