@dxos/functions 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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/README.md +4 -6
- package/dist/lib/neutral/Trace.mjs +34 -0
- package/dist/lib/neutral/Trace.mjs.map +7 -0
- package/dist/lib/neutral/chunk-BHLSCAA2.mjs +123 -0
- package/dist/lib/neutral/chunk-BHLSCAA2.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-Z2XDJJVH.mjs +49 -0
- package/dist/lib/neutral/chunk-Z2XDJJVH.mjs.map +7 -0
- package/dist/lib/neutral/fib-S6PPI4UW.mjs +23 -0
- package/dist/lib/neutral/fib-S6PPI4UW.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/index.mjs +649 -633
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/reply-TOHXEG7V.mjs +19 -0
- package/dist/lib/neutral/reply-TOHXEG7V.mjs.map +7 -0
- package/dist/lib/neutral/sleep-QPSZDPEH.mjs +15 -0
- package/dist/lib/neutral/sleep-QPSZDPEH.mjs.map +7 -0
- package/dist/types/src/Trace.d.ts +135 -0
- package/dist/types/src/Trace.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts.map +1 -1
- package/dist/types/src/example/definitions.d.ts +11 -0
- package/dist/types/src/example/definitions.d.ts.map +1 -0
- package/dist/types/src/example/fib.d.ts +3 -2
- package/dist/types/src/example/fib.d.ts.map +1 -1
- package/dist/types/src/example/index.d.ts +3 -11
- package/dist/types/src/example/index.d.ts.map +1 -1
- package/dist/types/src/example/reply.d.ts +2 -1
- package/dist/types/src/example/reply.d.ts.map +1 -1
- package/dist/types/src/example/sleep.d.ts +3 -2
- package/dist/types/src/example/sleep.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/process/Process.d.ts +247 -0
- package/dist/types/src/process/Process.d.ts.map +1 -0
- package/dist/types/src/process/ServiceResolver.d.ts +74 -0
- package/dist/types/src/process/ServiceResolver.d.ts.map +1 -0
- package/dist/types/src/process/StorageService.d.ts +58 -0
- package/dist/types/src/process/StorageService.d.ts.map +1 -0
- package/dist/types/src/protocol/protocol.d.ts +2 -2
- package/dist/types/src/protocol/protocol.d.ts.map +1 -1
- package/dist/types/src/sdk.d.ts +4 -104
- package/dist/types/src/sdk.d.ts.map +1 -1
- package/dist/types/src/services/event-logger.d.ts +4 -4
- package/dist/types/src/services/function-invocation-service.d.ts +6 -5
- package/dist/types/src/services/function-invocation-service.d.ts.map +1 -1
- package/dist/types/src/services/queues.d.ts +4 -2
- package/dist/types/src/services/queues.d.ts.map +1 -1
- package/dist/types/src/services/tracing.d.ts +25 -2
- package/dist/types/src/services/tracing.d.ts.map +1 -1
- package/dist/types/src/types/Script.d.ts +4 -3
- package/dist/types/src/types/Script.d.ts.map +1 -1
- package/dist/types/src/types/Trigger.d.ts +8 -9
- package/dist/types/src/types/Trigger.d.ts.map +1 -1
- package/dist/types/src/types/TriggerEvent.d.ts +3 -2
- package/dist/types/src/types/TriggerEvent.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +0 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/url.d.ts +2 -2
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -20
- package/src/Trace.ts +162 -0
- package/src/errors.ts +1 -1
- package/src/example/definitions.ts +48 -0
- package/src/example/fib.ts +14 -24
- package/src/example/forex-effect.ts +1 -1
- package/src/example/index.ts +7 -8
- package/src/example/reply.ts +10 -13
- package/src/example/sleep.ts +8 -17
- package/src/index.ts +4 -0
- package/src/process/Process.ts +457 -0
- package/src/process/ServiceResolver.ts +173 -0
- package/src/process/StorageService.ts +99 -0
- package/src/protocol/protocol.ts +33 -27
- package/src/sdk.ts +6 -256
- package/src/services/event-logger.ts +1 -1
- package/src/services/function-invocation-service.ts +6 -5
- package/src/services/queues.ts +10 -2
- package/src/services/tracing.ts +35 -2
- package/src/types/Script.ts +7 -3
- package/src/types/Trigger.ts +17 -6
- package/src/types/TriggerEvent.ts +2 -2
- package/src/types/index.ts +0 -1
- package/src/types/url.ts +2 -2
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -1230
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/types/src/operation-compatibility.test.d.ts +0 -2
- package/dist/types/src/operation-compatibility.test.d.ts.map +0 -1
- package/dist/types/src/types/Function.d.ts +0 -52
- package/dist/types/src/types/Function.d.ts.map +0 -1
- package/src/operation-compatibility.test.ts +0 -185
- package/src/types/Function.ts +0 -82
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Pipeable from 'effect/Pipeable';
|
|
6
|
+
import * as Context from 'effect/Context';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Option from 'effect/Option';
|
|
9
|
+
import * as Schema from 'effect/Schema';
|
|
10
|
+
|
|
11
|
+
export interface Service {
|
|
12
|
+
/** Read a value by key. Returns `None` if key does not exist. */
|
|
13
|
+
get<S extends Schema.Schema<any, string, any>>(
|
|
14
|
+
schema: S,
|
|
15
|
+
key: string,
|
|
16
|
+
): Effect.Effect<Option.Option<Schema.Schema.Type<S>>, never, Schema.Schema.Context<S>>;
|
|
17
|
+
|
|
18
|
+
/** Write a value for the given key. */
|
|
19
|
+
set<S extends Schema.Schema<any, string, any>>(
|
|
20
|
+
schema: S,
|
|
21
|
+
key: string,
|
|
22
|
+
value: Schema.Schema.Type<S>,
|
|
23
|
+
): Effect.Effect<void, never, Schema.Schema.Context<S>>;
|
|
24
|
+
|
|
25
|
+
/** Remove a key. */
|
|
26
|
+
delete(key: string): Effect.Effect<void>;
|
|
27
|
+
|
|
28
|
+
/** List all keys, optionally filtered by prefix. */
|
|
29
|
+
list(prefix?: string): Effect.Effect<readonly string[]>;
|
|
30
|
+
|
|
31
|
+
/** Remove all keys managed by this scoped store. */
|
|
32
|
+
clear(): Effect.Effect<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Scoped key-value storage service for processes.
|
|
37
|
+
* Each process receives its own namespaced instance via the process manager.
|
|
38
|
+
* Construct a live implementation with `StorageService.layer` from `@dxos/functions-runtime`.
|
|
39
|
+
*/
|
|
40
|
+
export class StorageService extends Context.Tag('@dxos/functions/StorageService')<StorageService, Service>() {}
|
|
41
|
+
|
|
42
|
+
export const get = Effect.serviceFunctionEffect(StorageService, (_) => _.get);
|
|
43
|
+
export const set = Effect.serviceFunctionEffect(StorageService, (_) => _.set);
|
|
44
|
+
export const deleteKey = Effect.serviceFunctionEffect(StorageService, (_) => _.delete);
|
|
45
|
+
export const list = Effect.serviceFunctionEffect(StorageService, (_) => _.list);
|
|
46
|
+
export const clear = Effect.serviceFunctionEffect(StorageService, (_) => _.clear);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Typed key in a storage service.
|
|
50
|
+
*/
|
|
51
|
+
export interface Key<T> extends Pipeable.Pipeable {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
|
|
54
|
+
get: Effect.Effect<Option.Option<T>, never, StorageService>;
|
|
55
|
+
set(value: T): Effect.Effect<void, never, StorageService>;
|
|
56
|
+
delete(): Effect.Effect<void, never, StorageService>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a typed key in a storage service.
|
|
61
|
+
*/
|
|
62
|
+
export const key = <S extends Schema.Schema<any, string, any>>(schema: S, key: string): Key<Schema.Schema.Type<S>> => {
|
|
63
|
+
return {
|
|
64
|
+
key,
|
|
65
|
+
get: get(schema, key),
|
|
66
|
+
set: (value: Schema.Schema.Type<S>) => set(schema, key, value),
|
|
67
|
+
delete: () => deleteKey(key),
|
|
68
|
+
pipe(...args: any) {
|
|
69
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Typed key in a storage service with a default value.
|
|
76
|
+
*/
|
|
77
|
+
export interface KeyWithDefault<T, U> extends Pipeable.Pipeable {
|
|
78
|
+
readonly key: string;
|
|
79
|
+
get: Effect.Effect<T | U, never, StorageService>;
|
|
80
|
+
set(value: U): Effect.Effect<void, never, StorageService>;
|
|
81
|
+
delete(): Effect.Effect<void, never, StorageService>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Assign a default value to a key if it is not present.
|
|
86
|
+
*/
|
|
87
|
+
export const withDefault =
|
|
88
|
+
<T>(getDefault: () => NoInfer<T>) =>
|
|
89
|
+
(key: Key<T>): KeyWithDefault<T, T> => {
|
|
90
|
+
return {
|
|
91
|
+
key: key.key,
|
|
92
|
+
get: key.get.pipe(Effect.map(Option.getOrElse(() => getDefault()))),
|
|
93
|
+
set: (value) => key.set(value),
|
|
94
|
+
delete: () => key.delete(),
|
|
95
|
+
pipe(...args: any) {
|
|
96
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
};
|
package/src/protocol/protocol.ts
CHANGED
|
@@ -11,40 +11,46 @@ import * as SchemaAST from 'effect/SchemaAST';
|
|
|
11
11
|
import { AiModelResolver, AiService } from '@dxos/ai';
|
|
12
12
|
import { AnthropicResolver } from '@dxos/ai/resolvers';
|
|
13
13
|
import { LifecycleState, Resource } from '@dxos/context';
|
|
14
|
-
import { Database, Ref, Type } from '@dxos/echo';
|
|
14
|
+
import { Database, Feed, JsonSchema, Ref, type Type } from '@dxos/echo';
|
|
15
15
|
import { refFromEncodedReference } from '@dxos/echo/internal';
|
|
16
|
-
import { EchoClient, type EchoDatabaseImpl, type QueueFactory } from '@dxos/echo-db';
|
|
16
|
+
import { EchoClient, type EchoDatabaseImpl, type QueueFactory, createFeedServiceLayer } from '@dxos/echo-db';
|
|
17
17
|
import { runAndForwardErrors } from '@dxos/effect';
|
|
18
18
|
import { assertState, failedInvariant, invariant } from '@dxos/invariant';
|
|
19
19
|
import { PublicKey } from '@dxos/keys';
|
|
20
20
|
import { type FunctionProtocol } from '@dxos/protocols';
|
|
21
21
|
|
|
22
22
|
import { FunctionError } from '../errors';
|
|
23
|
-
import {
|
|
23
|
+
import { type FunctionServices } from '../sdk';
|
|
24
24
|
import { CredentialsService, FunctionInvocationService, QueueService, TracingService } from '../services';
|
|
25
|
+
import * as Trace from '../Trace';
|
|
26
|
+
import { Operation } from '@dxos/operation';
|
|
25
27
|
|
|
26
28
|
import { FunctionsAiHttpClient } from './functions-ai-http-client';
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Wraps a function handler made with `defineFunction` to a protocol that the functions-runtime expects.
|
|
30
32
|
*/
|
|
31
|
-
export const wrapFunctionHandler = (func:
|
|
32
|
-
if (!
|
|
33
|
-
throw new TypeError('
|
|
33
|
+
export const wrapFunctionHandler = (func: Operation.WithHandler<Operation.Definition.Any>): FunctionProtocol.Func => {
|
|
34
|
+
if (!Operation.isOperationWithHandler(func)) {
|
|
35
|
+
throw new TypeError('Expected operation with handler');
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
const serviceTags = func.services.map((service) => service.key);
|
|
39
|
+
|
|
36
40
|
return {
|
|
37
41
|
meta: {
|
|
38
|
-
key: func.key,
|
|
39
|
-
name: func.name,
|
|
40
|
-
description: func.description,
|
|
41
|
-
inputSchema:
|
|
42
|
-
outputSchema: func.
|
|
43
|
-
services: func.services,
|
|
42
|
+
key: func.meta.key,
|
|
43
|
+
name: func.meta.name,
|
|
44
|
+
description: func.meta.description,
|
|
45
|
+
inputSchema: JsonSchema.toJsonSchema(func.input),
|
|
46
|
+
outputSchema: func.output === undefined ? undefined : JsonSchema.toJsonSchema(func.output),
|
|
47
|
+
services: func.services.map((service) => service.key),
|
|
44
48
|
},
|
|
45
49
|
handler: async ({ data, context }) => {
|
|
46
50
|
if (
|
|
47
|
-
(
|
|
51
|
+
(serviceTags.includes(Database.Service.key) ||
|
|
52
|
+
serviceTags.includes(QueueService.key) ||
|
|
53
|
+
serviceTags.includes(Feed.Service.key)) &&
|
|
48
54
|
(!context.services.dataService || !context.services.queryService)
|
|
49
55
|
) {
|
|
50
56
|
throw new FunctionError({
|
|
@@ -54,9 +60,9 @@ export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.
|
|
|
54
60
|
|
|
55
61
|
// eslint-disable-next-line no-useless-catch
|
|
56
62
|
try {
|
|
57
|
-
if (!SchemaAST.isAnyKeyword(func.
|
|
63
|
+
if (!SchemaAST.isAnyKeyword(func.input.ast)) {
|
|
58
64
|
try {
|
|
59
|
-
Schema.validateSync(func.
|
|
65
|
+
Schema.validateSync(func.input)(data);
|
|
60
66
|
} catch (error) {
|
|
61
67
|
throw new FunctionError({ message: 'Invalid input schema', cause: error });
|
|
62
68
|
}
|
|
@@ -66,19 +72,15 @@ export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.
|
|
|
66
72
|
|
|
67
73
|
if (func.types.length > 0) {
|
|
68
74
|
invariant(funcContext.db, 'Database is required for functions with types');
|
|
69
|
-
await funcContext.db.graph.schemaRegistry.register(func.types as Type.
|
|
75
|
+
await funcContext.db.graph.schemaRegistry.register(func.types as Type.AnyEntity[]);
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
const dataWithDecodedRefs =
|
|
73
|
-
funcContext.db && !SchemaAST.isAnyKeyword(func.
|
|
74
|
-
? decodeRefsFromSchema(func.
|
|
79
|
+
funcContext.db && !SchemaAST.isAnyKeyword(func.input.ast)
|
|
80
|
+
? decodeRefsFromSchema(func.input.ast, data, funcContext.db)
|
|
75
81
|
: data;
|
|
76
82
|
|
|
77
|
-
let result = await func.handler(
|
|
78
|
-
// TODO(dmaretskyi): Fix the types.
|
|
79
|
-
context: context as any,
|
|
80
|
-
data: dataWithDecodedRefs,
|
|
81
|
-
});
|
|
83
|
+
let result: any = await func.handler(dataWithDecodedRefs);
|
|
82
84
|
|
|
83
85
|
if (Effect.isEffect(result)) {
|
|
84
86
|
result = await runAndForwardErrors(
|
|
@@ -89,8 +91,8 @@ export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.
|
|
|
89
91
|
);
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
if (func.
|
|
93
|
-
Schema.validateSync(func.
|
|
94
|
+
if (func.output && !SchemaAST.isAnyKeyword(func.output.ast)) {
|
|
95
|
+
Schema.validateSync(func.output)(result);
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
return result;
|
|
@@ -149,8 +151,9 @@ class FunctionContext extends Resource {
|
|
|
149
151
|
createLayer(): Layer.Layer<FunctionServices> {
|
|
150
152
|
assertState(this._lifecycleState === LifecycleState.OPEN, 'FunctionContext is not open');
|
|
151
153
|
|
|
152
|
-
const dbLayer = this.db ? Database.
|
|
154
|
+
const dbLayer = this.db ? Database.layer(this.db) : Database.notAvailable;
|
|
153
155
|
const queuesLayer = this.queues ? QueueService.layer(this.queues) : QueueService.notAvailable;
|
|
156
|
+
const feedLayer = this.queues ? createFeedServiceLayer(this.queues) : Feed.notAvailable;
|
|
154
157
|
const credentials = dbLayer
|
|
155
158
|
? CredentialsService.layerFromDatabase({ caching: true }).pipe(Layer.provide(dbLayer))
|
|
156
159
|
: CredentialsService.configuredLayer([]);
|
|
@@ -173,12 +176,15 @@ class FunctionContext extends Resource {
|
|
|
173
176
|
: AiService.notAvailable;
|
|
174
177
|
|
|
175
178
|
return Layer.mergeAll(
|
|
176
|
-
dbLayer,
|
|
179
|
+
dbLayer,
|
|
177
180
|
queuesLayer,
|
|
181
|
+
feedLayer,
|
|
178
182
|
credentials,
|
|
179
183
|
functionInvocationService,
|
|
180
184
|
aiLayer,
|
|
181
185
|
tracing,
|
|
186
|
+
// TODO(dmaretskyi): Forward trace events.
|
|
187
|
+
Trace.writerLayerNoop,
|
|
182
188
|
);
|
|
183
189
|
}
|
|
184
190
|
}
|
package/src/sdk.ts
CHANGED
|
@@ -2,15 +2,9 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type * as Context from 'effect/Context';
|
|
6
|
-
import * as Effect from 'effect/Effect';
|
|
7
|
-
import * as Schema from 'effect/Schema';
|
|
8
|
-
|
|
9
5
|
import { type AiService } from '@dxos/ai';
|
|
10
|
-
import {
|
|
11
|
-
import { type
|
|
12
|
-
import { assertArgument, failedInvariant } from '@dxos/invariant';
|
|
13
|
-
import { Operation } from '@dxos/operation';
|
|
6
|
+
import { type Database, type Feed } from '@dxos/echo';
|
|
7
|
+
import { type Trace } from '@dxos/functions';
|
|
14
8
|
|
|
15
9
|
import {
|
|
16
10
|
type CredentialsService,
|
|
@@ -18,8 +12,6 @@ import {
|
|
|
18
12
|
type QueueService,
|
|
19
13
|
type TracingService,
|
|
20
14
|
} from './services';
|
|
21
|
-
import { Function } from './types';
|
|
22
|
-
import { getUserFunctionIdInMetadata, setUserFunctionIdInMetadata } from './types';
|
|
23
15
|
|
|
24
16
|
// TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
|
|
25
17
|
// https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
|
|
@@ -33,257 +25,15 @@ export type InvocationServices = TracingService;
|
|
|
33
25
|
|
|
34
26
|
/**
|
|
35
27
|
* Services that are available to invoked functions.
|
|
28
|
+
* @deprecated
|
|
36
29
|
*/
|
|
37
30
|
export type FunctionServices =
|
|
38
31
|
| InvocationServices
|
|
39
32
|
| AiService.AiService
|
|
40
33
|
| CredentialsService
|
|
41
34
|
| Database.Service
|
|
35
|
+
// TODO(wittjosiah): Remove QueueService — use Feed.Service instead.
|
|
42
36
|
| QueueService
|
|
37
|
+
| Feed.Service
|
|
38
|
+
| Trace.TraceService
|
|
43
39
|
| FunctionInvocationService;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Function handler.
|
|
47
|
-
*/
|
|
48
|
-
export type FunctionHandler<TData = {}, TOutput = any, S extends FunctionServices = FunctionServices> = (params: {
|
|
49
|
-
/**
|
|
50
|
-
* Context available to the function.
|
|
51
|
-
*/
|
|
52
|
-
context: FunctionContext;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Data passed as the input to the function.
|
|
56
|
-
* Must match the function's input schema.
|
|
57
|
-
* This will be the payload from the trigger or other data passed into the function in a workflow.
|
|
58
|
-
*/
|
|
59
|
-
data: TData;
|
|
60
|
-
}) => TOutput | Promise<TOutput> | Effect.Effect<TOutput, any, S>;
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Function context.
|
|
64
|
-
*/
|
|
65
|
-
export interface FunctionContext {
|
|
66
|
-
// TODO(dmaretskyi): Consider what we should put into context.
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const typeId = Symbol.for('@dxos/functions/FunctionDefinition');
|
|
70
|
-
|
|
71
|
-
export type FunctionDefinition<T = any, O = any, S extends FunctionServices = FunctionServices> = {
|
|
72
|
-
[typeId]: true;
|
|
73
|
-
key: string;
|
|
74
|
-
name: string;
|
|
75
|
-
description?: string;
|
|
76
|
-
inputSchema: Schema.Schema<T, any>;
|
|
77
|
-
outputSchema?: Schema.Schema<O, any>;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* List of types the function uses.
|
|
81
|
-
* This is used to ensure that the types are available when the function is executed.
|
|
82
|
-
*/
|
|
83
|
-
types: readonly Type.Entity.Any[];
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Keys of the required services.
|
|
87
|
-
*/
|
|
88
|
-
services: readonly string[];
|
|
89
|
-
|
|
90
|
-
handler: FunctionHandler<T, O, S>;
|
|
91
|
-
meta?: {
|
|
92
|
-
/**
|
|
93
|
-
* Tools that are projected from functions have this annotation.
|
|
94
|
-
*
|
|
95
|
-
* deployedFunctionId:
|
|
96
|
-
* - Backend deployment ID assigned by the EDGE function service (typically a UUID).
|
|
97
|
-
* - Used for remote invocation via `FunctionInvocationService` → `RemoteFunctionExecutionService`.
|
|
98
|
-
* - Persisted on the corresponding ECHO `Function.Function` object's metadata under the
|
|
99
|
-
* `FUNCTIONS_META_KEY` and retrieved with `getUserFunctionIdInMetadata`.
|
|
100
|
-
*/
|
|
101
|
-
deployedFunctionId?: string;
|
|
102
|
-
};
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export declare namespace FunctionDefinition {
|
|
106
|
-
export type Any = FunctionDefinition<any, any, any>;
|
|
107
|
-
export type Input<T extends Any> = T extends FunctionDefinition<infer I, infer _O, infer _S> ? I : never;
|
|
108
|
-
export type Output<T extends Any> = T extends FunctionDefinition<infer _I, infer O, infer _S> ? O : never;
|
|
109
|
-
export type Services<T extends Any> = T extends FunctionDefinition<infer _I, infer _O, infer S> ? S : never;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type FunctionProps<T, O> = {
|
|
113
|
-
key: string;
|
|
114
|
-
name: string;
|
|
115
|
-
description?: string;
|
|
116
|
-
inputSchema: Schema.Schema<T, any>;
|
|
117
|
-
outputSchema?: Schema.Schema<O, any>;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* List of types the function uses.
|
|
121
|
-
* This is used to ensure that the types are available when the function is executed.
|
|
122
|
-
*/
|
|
123
|
-
types?: readonly Type.Entity.Any[];
|
|
124
|
-
// TODO(dmaretskyi): This currently doesn't cause a compile-time error if the handler requests a service that is not specified
|
|
125
|
-
services?: readonly Context.Tag<any, any>[];
|
|
126
|
-
|
|
127
|
-
handler: FunctionHandler<T, O, FunctionServices>;
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// TODO(dmaretskyi): Output type doesn't get typechecked.
|
|
131
|
-
export const defineFunction: {
|
|
132
|
-
<I, O>(params: FunctionProps<I, O>): FunctionDefinition<I, O, FunctionServices>;
|
|
133
|
-
} = ({ key, name, description, inputSchema, outputSchema = Schema.Any, handler, types, services }) => {
|
|
134
|
-
if (!Schema.isSchema(inputSchema)) {
|
|
135
|
-
throw new Error('Input schema must be a valid schema');
|
|
136
|
-
}
|
|
137
|
-
if (typeof handler !== 'function') {
|
|
138
|
-
throw new Error('Handler must be a function');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Captures the function definition location.
|
|
142
|
-
const limit = Error.stackTraceLimit;
|
|
143
|
-
Error.stackTraceLimit = 2;
|
|
144
|
-
const traceError = new Error();
|
|
145
|
-
Error.stackTraceLimit = limit;
|
|
146
|
-
let cache: false | string = false;
|
|
147
|
-
const captureStackTrace = () => {
|
|
148
|
-
if (cache !== false) {
|
|
149
|
-
return cache;
|
|
150
|
-
}
|
|
151
|
-
if (traceError.stack !== undefined) {
|
|
152
|
-
const stack = traceError.stack.split('\n');
|
|
153
|
-
if (stack[2] !== undefined) {
|
|
154
|
-
cache = stack[2].trim();
|
|
155
|
-
return cache;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const handlerWithSpan = (...args: any[]) => {
|
|
161
|
-
const result = (handler as any)(...args);
|
|
162
|
-
if (Effect.isEffect(result)) {
|
|
163
|
-
return Effect.withSpan(result, `${key ?? name}`, {
|
|
164
|
-
captureStackTrace,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
return result;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
[typeId]: true,
|
|
172
|
-
key,
|
|
173
|
-
name,
|
|
174
|
-
description,
|
|
175
|
-
inputSchema,
|
|
176
|
-
outputSchema,
|
|
177
|
-
handler: handlerWithSpan,
|
|
178
|
-
types: types ?? [],
|
|
179
|
-
services: !services ? [] : getServiceKeys(services),
|
|
180
|
-
} satisfies FunctionDefinition.Any;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const getServiceKeys = (services: readonly Context.Tag<any, any>[]) => {
|
|
184
|
-
return services.map((tag: any) => {
|
|
185
|
-
if (typeof tag.key === 'string') {
|
|
186
|
-
return tag.key;
|
|
187
|
-
}
|
|
188
|
-
failedInvariant();
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Converts a FunctionDefinition to an OperationDefinition with handler.
|
|
194
|
-
* The function handler is adapted to the OperationHandler format.
|
|
195
|
-
*
|
|
196
|
-
* Note: FunctionDefinition stores service keys as strings, not Tag types,
|
|
197
|
-
* so we can't use Operation.withHandler's type inference here.
|
|
198
|
-
*/
|
|
199
|
-
export const toOperation = <T, O, S extends FunctionServices = FunctionServices>(
|
|
200
|
-
functionDef: FunctionDefinition<T, O, S>,
|
|
201
|
-
): Operation.Definition<T, O> & { handler: Operation.Handler<T, O, any, S> } => {
|
|
202
|
-
const op = Operation.make({
|
|
203
|
-
schema: {
|
|
204
|
-
input: functionDef.inputSchema,
|
|
205
|
-
output: functionDef.outputSchema ?? Schema.Any,
|
|
206
|
-
},
|
|
207
|
-
meta: {
|
|
208
|
-
key: functionDef.key,
|
|
209
|
-
name: functionDef.name,
|
|
210
|
-
description: functionDef.description,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Adapt FunctionHandler signature to OperationHandler format.
|
|
215
|
-
// FunctionHandler expects { context, data }, OperationHandler expects just input.
|
|
216
|
-
const operationHandler: Operation.Handler<T, O, any, S> = (input: T) => {
|
|
217
|
-
const result = functionDef.handler({
|
|
218
|
-
context: {} as FunctionContext,
|
|
219
|
-
data: input,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
// Convert Promise or plain value to Effect.
|
|
223
|
-
if (Effect.isEffect(result)) {
|
|
224
|
-
return result;
|
|
225
|
-
}
|
|
226
|
-
if (result instanceof Promise) {
|
|
227
|
-
return Effect.tryPromise(() => result);
|
|
228
|
-
}
|
|
229
|
-
return Effect.succeed(result as O);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// Manually attach handler since FunctionDefinition stores service keys as strings,
|
|
233
|
-
// not Tag types, so withHandler's type inference doesn't apply.
|
|
234
|
-
return {
|
|
235
|
-
...op,
|
|
236
|
-
handler: operationHandler,
|
|
237
|
-
};
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
export const FunctionDefinition = {
|
|
241
|
-
make: defineFunction,
|
|
242
|
-
isFunction: (value: unknown): value is FunctionDefinition.Any => {
|
|
243
|
-
return typeof value === 'object' && value !== null && Symbol.for('@dxos/functions/FunctionDefinition') in value;
|
|
244
|
-
},
|
|
245
|
-
serialize: (functionDef: FunctionDefinition.Any): Function.Function => {
|
|
246
|
-
assertArgument(FunctionDefinition.isFunction(functionDef), 'functionDef');
|
|
247
|
-
return serializeFunction(functionDef);
|
|
248
|
-
},
|
|
249
|
-
deserialize: (functionObj: Function.Function): FunctionDefinition.Any => {
|
|
250
|
-
assertArgument(Obj.instanceOf(Function.Function, functionObj), 'functionObj');
|
|
251
|
-
return deserializeFunction(functionObj);
|
|
252
|
-
},
|
|
253
|
-
toOperation,
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
export const serializeFunction = (functionDef: FunctionDefinition.Any): Function.Function => {
|
|
257
|
-
const fn = Function.make({
|
|
258
|
-
key: functionDef.key,
|
|
259
|
-
name: functionDef.name,
|
|
260
|
-
version: '0.1.0',
|
|
261
|
-
description: functionDef.description,
|
|
262
|
-
inputSchema: Type.toJsonSchema(functionDef.inputSchema),
|
|
263
|
-
outputSchema: !functionDef.outputSchema ? undefined : Type.toJsonSchema(functionDef.outputSchema),
|
|
264
|
-
services: functionDef.services,
|
|
265
|
-
});
|
|
266
|
-
if (functionDef.meta?.deployedFunctionId) {
|
|
267
|
-
Obj.change(fn, (fn) => setUserFunctionIdInMetadata(Obj.getMeta(fn), functionDef.meta!.deployedFunctionId!));
|
|
268
|
-
}
|
|
269
|
-
return fn;
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
export const deserializeFunction = (functionObj: Function.Function): FunctionDefinition<unknown, unknown> => {
|
|
273
|
-
return {
|
|
274
|
-
[typeId]: true,
|
|
275
|
-
// TODO(dmaretskyi): Fix key.
|
|
276
|
-
key: functionObj.key ?? functionObj.name,
|
|
277
|
-
name: functionObj.name,
|
|
278
|
-
description: functionObj.description,
|
|
279
|
-
inputSchema: !functionObj.inputSchema ? Schema.Unknown : Type.toEffectSchema(functionObj.inputSchema),
|
|
280
|
-
outputSchema: !functionObj.outputSchema ? undefined : Type.toEffectSchema(functionObj.outputSchema),
|
|
281
|
-
// TODO(dmaretskyi): This should throw error.
|
|
282
|
-
handler: () => {},
|
|
283
|
-
services: functionObj.services ?? [],
|
|
284
|
-
types: [],
|
|
285
|
-
meta: {
|
|
286
|
-
deployedFunctionId: getUserFunctionIdInMetadata(Obj.getMeta(functionObj)),
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
};
|
|
@@ -52,7 +52,7 @@ export type ComputeEventPayload = Schema.Schema.Type<typeof ComputeEventPayload>
|
|
|
52
52
|
|
|
53
53
|
export const ComputeEvent = Schema.Struct({
|
|
54
54
|
payload: ComputeEventPayload,
|
|
55
|
-
}).pipe(Type.object({ typename: 'dxos.
|
|
55
|
+
}).pipe(Type.object({ typename: 'org.dxos.type.computeEvent', version: '0.1.0' }));
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Logs event for the compute workflows.
|
|
@@ -6,17 +6,18 @@ import * as Effect from 'effect/Effect';
|
|
|
6
6
|
import * as Layer from 'effect/Layer';
|
|
7
7
|
|
|
8
8
|
import type { FunctionNotFoundError } from '../errors';
|
|
9
|
-
import { type
|
|
9
|
+
import { type InvocationServices } from '../sdk';
|
|
10
|
+
import { Operation } from '@dxos/operation';
|
|
10
11
|
|
|
11
12
|
export class FunctionInvocationService extends Context.Tag('@dxos/functions/FunctionInvocationService')<
|
|
12
13
|
FunctionInvocationService,
|
|
13
14
|
{
|
|
14
15
|
invokeFunction<I, O>(
|
|
15
|
-
functionDef:
|
|
16
|
+
functionDef: Operation.Definition<I, O, any>,
|
|
16
17
|
input: I,
|
|
17
18
|
): Effect.Effect<O, never, InvocationServices>;
|
|
18
19
|
|
|
19
|
-
resolveFunction(key: string): Effect.Effect<
|
|
20
|
+
resolveFunction(key: string): Effect.Effect<Operation.Definition.Any, FunctionNotFoundError>;
|
|
20
21
|
}
|
|
21
22
|
>() {
|
|
22
23
|
static layerNotAvailable = Layer.succeed(FunctionInvocationService, {
|
|
@@ -25,13 +26,13 @@ export class FunctionInvocationService extends Context.Tag('@dxos/functions/Func
|
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
static invokeFunction = <I, O>(
|
|
28
|
-
functionDef:
|
|
29
|
+
functionDef: Operation.Definition<I, O, any>,
|
|
29
30
|
input: I,
|
|
30
31
|
): Effect.Effect<O, never, FunctionInvocationService | InvocationServices> =>
|
|
31
32
|
Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.invokeFunction)(functionDef, input);
|
|
32
33
|
|
|
33
34
|
static resolveFunction = (
|
|
34
35
|
key: string,
|
|
35
|
-
): Effect.Effect<
|
|
36
|
+
): Effect.Effect<Operation.Definition.Any, FunctionNotFoundError, FunctionInvocationService> =>
|
|
36
37
|
Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.resolveFunction)(key);
|
|
37
38
|
}
|
package/src/services/queues.ts
CHANGED
|
@@ -6,12 +6,13 @@ import * as Context from 'effect/Context';
|
|
|
6
6
|
import * as Effect from 'effect/Effect';
|
|
7
7
|
import * as Layer from 'effect/Layer';
|
|
8
8
|
|
|
9
|
-
import type
|
|
10
|
-
import type
|
|
9
|
+
import { type Entity } from '@dxos/echo';
|
|
10
|
+
import { createFeedServiceLayer, type Queue, type QueueAPI, type QueueFactory } from '@dxos/echo-db';
|
|
11
11
|
import type { DXN, QueueSubspaceTag } from '@dxos/keys';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Gives access to all queues.
|
|
15
|
+
* @deprecated Use FeedService instead.
|
|
15
16
|
*/
|
|
16
17
|
export class QueueService extends Context.Tag('@dxos/functions/QueueService')<
|
|
17
18
|
QueueService,
|
|
@@ -80,3 +81,10 @@ export class ContextQueueService extends Context.Tag('@dxos/functions/ContextQue
|
|
|
80
81
|
>() {
|
|
81
82
|
static layer = (queue: Queue) => Layer.succeed(ContextQueueService, { queue });
|
|
82
83
|
}
|
|
84
|
+
|
|
85
|
+
export const feedServiceFromQueueServiceLayer = Layer.unwrapEffect(
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const { queues } = yield* QueueService;
|
|
88
|
+
return createFeedServiceLayer(queues);
|
|
89
|
+
}),
|
|
90
|
+
);
|
package/src/services/tracing.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as Effect from 'effect/Effect';
|
|
|
7
7
|
import * as Layer from 'effect/Layer';
|
|
8
8
|
|
|
9
9
|
import { AgentStatus } from '@dxos/ai';
|
|
10
|
-
import { type DXN, Obj } from '@dxos/echo';
|
|
10
|
+
import { type DXN, Obj, Ref } from '@dxos/echo';
|
|
11
11
|
import { ObjectId } from '@dxos/keys';
|
|
12
12
|
import { Message } from '@dxos/types';
|
|
13
13
|
|
|
@@ -16,6 +16,7 @@ import type { Trigger } from '../types';
|
|
|
16
16
|
/**
|
|
17
17
|
* Provides a way for compute primitives (functions, workflows, tools)
|
|
18
18
|
* to emit an execution trace as a series of structured ECHO objects.
|
|
19
|
+
* @deprecated Use Trace.TraceService instead.
|
|
19
20
|
*/
|
|
20
21
|
export class TracingService extends Context.Tag('@dxos/functions/TracingService')<
|
|
21
22
|
TracingService,
|
|
@@ -31,6 +32,11 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
31
32
|
*/
|
|
32
33
|
write: (event: Obj.Unknown, traceContext: TracingService.TraceContext) => void;
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Write an ephemeral event.
|
|
37
|
+
*/
|
|
38
|
+
ephemeral: (event: Obj.Unknown, traceContext: TracingService.TraceContext) => void;
|
|
39
|
+
|
|
34
40
|
traceInvocationStart({
|
|
35
41
|
payload,
|
|
36
42
|
target,
|
|
@@ -51,6 +57,7 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
51
57
|
static noop: Context.Tag.Service<TracingService> = {
|
|
52
58
|
getTraceContext: () => ({}),
|
|
53
59
|
write: () => {},
|
|
60
|
+
ephemeral: () => {},
|
|
54
61
|
traceInvocationStart: () =>
|
|
55
62
|
Effect.sync(() => ({ invocationId: ObjectId.random(), invocationTraceQueue: undefined })),
|
|
56
63
|
traceInvocationEnd: () => Effect.sync(() => {}),
|
|
@@ -69,6 +76,7 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
69
76
|
const context = mapContext(tracing.getTraceContext());
|
|
70
77
|
return {
|
|
71
78
|
write: (event, context) => tracing.write(event, context),
|
|
79
|
+
ephemeral: (event, context) => tracing.ephemeral(event, context),
|
|
72
80
|
getTraceContext: () => context,
|
|
73
81
|
traceInvocationStart: () => Effect.die('Tracing invocation inside another invocation is not supported.'),
|
|
74
82
|
traceInvocationEnd: () => Effect.die('Tracing invocation inside another invocation is not supported.'),
|
|
@@ -106,7 +114,7 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
106
114
|
});
|
|
107
115
|
|
|
108
116
|
static emitConverationMessage: (
|
|
109
|
-
data: Obj.MakeProps<typeof Message.Message
|
|
117
|
+
data: Obj.MakeProps<typeof Message.Message> | Message.Message,
|
|
110
118
|
) => Effect.Effect<void, never, TracingService> = Effect.fnUntraced(function* (data) {
|
|
111
119
|
const tracing = yield* TracingService;
|
|
112
120
|
tracing.write(
|
|
@@ -121,6 +129,12 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
121
129
|
tracing.getTraceContext(),
|
|
122
130
|
);
|
|
123
131
|
});
|
|
132
|
+
|
|
133
|
+
static emitEphemeralMessage: (data: Message.Message) => Effect.Effect<void, never, TracingService> =
|
|
134
|
+
Effect.fnUntraced(function* (data) {
|
|
135
|
+
const tracing = yield* TracingService;
|
|
136
|
+
tracing.ephemeral(data, tracing.getTraceContext());
|
|
137
|
+
});
|
|
124
138
|
}
|
|
125
139
|
|
|
126
140
|
export namespace TracingService {
|
|
@@ -158,6 +172,25 @@ export namespace TracingService {
|
|
|
158
172
|
id: string;
|
|
159
173
|
kind: Trigger.Kind;
|
|
160
174
|
};
|
|
175
|
+
chat?: Ref.Ref<Obj.Unknown>;
|
|
176
|
+
process?: {
|
|
177
|
+
pid: string;
|
|
178
|
+
parentPid?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Key of the executable.
|
|
181
|
+
*/
|
|
182
|
+
key: string;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Process name.
|
|
186
|
+
*/
|
|
187
|
+
name?: string;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Target object that the process is assigned to.
|
|
191
|
+
*/
|
|
192
|
+
target?: string;
|
|
193
|
+
};
|
|
161
194
|
}
|
|
162
195
|
}
|
|
163
196
|
|