@dxos/functions 0.8.4-main.7ace549 → 0.8.4-main.937b3ca

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 (52) hide show
  1. package/dist/lib/browser/index.mjs +273 -74
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +273 -74
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/errors.d.ts +24 -32
  8. package/dist/types/src/errors.d.ts.map +1 -1
  9. package/dist/types/src/operation-compatibility.test.d.ts +2 -0
  10. package/dist/types/src/operation-compatibility.test.d.ts.map +1 -0
  11. package/dist/types/src/protocol/functions-ai-http-client.d.ts +12 -0
  12. package/dist/types/src/protocol/functions-ai-http-client.d.ts.map +1 -0
  13. package/dist/types/src/protocol/protocol.d.ts.map +1 -1
  14. package/dist/types/src/sdk.d.ts +18 -4
  15. package/dist/types/src/sdk.d.ts.map +1 -1
  16. package/dist/types/src/services/credentials.d.ts +6 -4
  17. package/dist/types/src/services/credentials.d.ts.map +1 -1
  18. package/dist/types/src/services/event-logger.d.ts +25 -31
  19. package/dist/types/src/services/event-logger.d.ts.map +1 -1
  20. package/dist/types/src/services/function-invocation-service.d.ts +5 -0
  21. package/dist/types/src/services/function-invocation-service.d.ts.map +1 -1
  22. package/dist/types/src/services/index.d.ts +0 -1
  23. package/dist/types/src/services/index.d.ts.map +1 -1
  24. package/dist/types/src/services/tracing.d.ts +37 -3
  25. package/dist/types/src/services/tracing.d.ts.map +1 -1
  26. package/dist/types/src/types/Function.d.ts +33 -44
  27. package/dist/types/src/types/Function.d.ts.map +1 -1
  28. package/dist/types/src/types/Script.d.ts +8 -15
  29. package/dist/types/src/types/Script.d.ts.map +1 -1
  30. package/dist/types/src/types/Trigger.d.ts +37 -57
  31. package/dist/types/src/types/Trigger.d.ts.map +1 -1
  32. package/dist/types/src/types/TriggerEvent.d.ts +33 -2
  33. package/dist/types/src/types/TriggerEvent.d.ts.map +1 -1
  34. package/dist/types/src/types/url.d.ts +4 -3
  35. package/dist/types/src/types/url.d.ts.map +1 -1
  36. package/dist/types/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +22 -16
  38. package/src/errors.ts +4 -4
  39. package/src/operation-compatibility.test.ts +185 -0
  40. package/src/protocol/functions-ai-http-client.ts +67 -0
  41. package/src/protocol/protocol.ts +118 -12
  42. package/src/sdk.ts +55 -6
  43. package/src/services/credentials.ts +31 -15
  44. package/src/services/event-logger.ts +2 -2
  45. package/src/services/function-invocation-service.ts +14 -0
  46. package/src/services/index.ts +0 -2
  47. package/src/services/tracing.ts +63 -4
  48. package/src/types/Function.ts +12 -10
  49. package/src/types/Script.ts +3 -2
  50. package/src/types/Trigger.ts +9 -6
  51. package/src/types/TriggerEvent.ts +9 -3
  52. package/src/types/url.ts +4 -3
@@ -11,7 +11,7 @@ import * as Layer from 'effect/Layer';
11
11
  import * as Redacted from 'effect/Redacted';
12
12
 
13
13
  import { Query } from '@dxos/echo';
14
- import { DatabaseService } from '@dxos/echo-db';
14
+ import { Database } from '@dxos/echo';
15
15
  import { AccessToken } from '@dxos/types';
16
16
 
17
17
  export type CredentialQuery = {
@@ -60,7 +60,12 @@ export class CredentialsService extends Context.Tag('@dxos/functions/Credentials
60
60
  static configuredLayer = (credentials: ServiceCredential[]) =>
61
61
  Layer.succeed(CredentialsService, new ConfiguredCredentialsService(credentials));
62
62
 
63
- static layerConfig = (credentials: { service: string; apiKey: Config.Config<Redacted.Redacted<string>> }[]) =>
63
+ static layerConfig = (
64
+ credentials: {
65
+ service: string;
66
+ apiKey: Config.Config<Redacted.Redacted<string>>;
67
+ }[],
68
+ ) =>
64
69
  Layer.effect(
65
70
  CredentialsService,
66
71
  Effect.gen(function* () {
@@ -77,20 +82,34 @@ export class CredentialsService extends Context.Tag('@dxos/functions/Credentials
77
82
  }),
78
83
  );
79
84
 
80
- static layerFromDatabase = () =>
85
+ static layerFromDatabase = ({ caching = false }: { caching?: boolean } = {}) =>
81
86
  Layer.effect(
82
87
  CredentialsService,
83
88
  Effect.gen(function* () {
84
- const dbService = yield* DatabaseService;
89
+ const dbService = yield* Database.Service;
90
+ const cache = new Map<string, ServiceCredential[]>();
91
+
85
92
  const queryCredentials = async (query: CredentialQuery): Promise<ServiceCredential[]> => {
86
- const { objects: accessTokens } = await dbService.db.query(Query.type(AccessToken.AccessToken)).run();
87
- return accessTokens
93
+ const cacheKey = JSON.stringify(query);
94
+ if (caching && cache.has(cacheKey)) {
95
+ return cache.get(cacheKey)!;
96
+ }
97
+
98
+ const accessTokens = await dbService.db.query(Query.type(AccessToken.AccessToken)).run();
99
+ const credentials = accessTokens
88
100
  .filter((accessToken) => accessToken.source === query.service)
89
101
  .map((accessToken) => ({
90
102
  service: accessToken.source,
91
103
  apiKey: accessToken.token,
92
104
  }));
105
+
106
+ if (caching) {
107
+ cache.set(cacheKey, credentials);
108
+ }
109
+
110
+ return credentials;
93
111
  };
112
+
94
113
  return {
95
114
  getCredential: async (query) => {
96
115
  const credentials = await queryCredentials(query);
@@ -131,13 +150,10 @@ export class ConfiguredCredentialsService implements Context.Tag.Service<Credent
131
150
  }
132
151
 
133
152
  /**
134
- * Maps the request to include the API key from the credential.
153
+ * Maps the request to include the given token in the Authorization header.
135
154
  */
136
- export const withAuthorization = (query: CredentialQuery, kind?: 'Bearer' | 'Basic') =>
137
- HttpClient.mapRequestEffect(
138
- Effect.fnUntraced(function* (request) {
139
- const key = yield* CredentialsService.getApiKey(query).pipe(Effect.map(Redacted.value));
140
- const authorization = kind ? `${kind} ${key}` : key;
141
- return HttpClientRequest.setHeader(request, 'Authorization', authorization);
142
- }),
143
- );
155
+ export const withAuthorization = (token: string, kind?: 'Bearer' | 'Basic') =>
156
+ HttpClient.mapRequest((request) => {
157
+ const authorization = kind ? `${kind} ${token}` : token;
158
+ return HttpClientRequest.setHeader(request, 'Authorization', authorization);
159
+ });
@@ -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.Obj({ typename: 'dxos.org/type/ComputeEvent', version: '0.1.0' }));
55
+ }).pipe(Type.object({ typename: 'dxos.org/type/ComputeEvent', version: '0.1.0' }));
56
56
 
57
57
  /**
58
58
  * Logs event for the compute workflows.
@@ -75,7 +75,7 @@ export class ComputeEventLogger extends Context.Tag('@dxos/functions/ComputeEven
75
75
  const tracing = yield* TracingService;
76
76
  return {
77
77
  log: (event: ComputeEventPayload) => {
78
- tracing.write(Obj.make(ComputeEvent, { payload: event }));
78
+ tracing.write(Obj.make(ComputeEvent, { payload: event }), tracing.getTraceContext());
79
79
  },
80
80
  nodeId: undefined,
81
81
  };
@@ -3,7 +3,9 @@
3
3
  //
4
4
  import * as Context from 'effect/Context';
5
5
  import * as Effect from 'effect/Effect';
6
+ import * as Layer from 'effect/Layer';
6
7
 
8
+ import type { FunctionNotFoundError } from '../errors';
7
9
  import { type FunctionDefinition, type InvocationServices } from '../sdk';
8
10
 
9
11
  export class FunctionInvocationService extends Context.Tag('@dxos/functions/FunctionInvocationService')<
@@ -13,11 +15,23 @@ export class FunctionInvocationService extends Context.Tag('@dxos/functions/Func
13
15
  functionDef: FunctionDefinition<I, O, any>,
14
16
  input: I,
15
17
  ): Effect.Effect<O, never, InvocationServices>;
18
+
19
+ resolveFunction(key: string): Effect.Effect<FunctionDefinition.Any, FunctionNotFoundError>;
16
20
  }
17
21
  >() {
22
+ static layerNotAvailable = Layer.succeed(FunctionInvocationService, {
23
+ invokeFunction: () => Effect.die('FunctionInvocationService is not avaialble.'),
24
+ resolveFunction: () => Effect.die('FunctionInvocationService is not available.'),
25
+ });
26
+
18
27
  static invokeFunction = <I, O>(
19
28
  functionDef: FunctionDefinition<I, O, any>,
20
29
  input: I,
21
30
  ): Effect.Effect<O, never, FunctionInvocationService | InvocationServices> =>
22
31
  Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.invokeFunction)(functionDef, input);
32
+
33
+ static resolveFunction = (
34
+ key: string,
35
+ ): Effect.Effect<FunctionDefinition.Any, FunctionNotFoundError, FunctionInvocationService> =>
36
+ Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.resolveFunction)(key);
23
37
  }
@@ -2,8 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export { DatabaseService } from '@dxos/echo-db';
6
-
7
5
  export * from './credentials';
8
6
  export { ConfiguredCredentialsService, type ServiceCredential } from './credentials';
9
7
  export * from './event-logger';
@@ -7,10 +7,12 @@ 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 { Obj } from '@dxos/echo';
11
- import { type ObjectId } from '@dxos/keys';
10
+ import { type DXN, Obj } from '@dxos/echo';
11
+ import { ObjectId } from '@dxos/keys';
12
12
  import { Message } from '@dxos/types';
13
13
 
14
+ import type { Trigger } from '../types';
15
+
14
16
  /**
15
17
  * Provides a way for compute primitives (functions, workflows, tools)
16
18
  * to emit an execution trace as a series of structured ECHO objects.
@@ -27,15 +29,35 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
27
29
  * Write an event to the tracing queue.
28
30
  * @param event - The event to write. Must be an a typed object.
29
31
  */
30
- write: (event: Obj.Any) => void;
32
+ write: (event: Obj.Unknown, traceContext: TracingService.TraceContext) => void;
33
+
34
+ traceInvocationStart({
35
+ payload,
36
+ target,
37
+ }: {
38
+ payload: TracingService.FunctionInvocationPayload;
39
+ target?: DXN;
40
+ }): Effect.Effect<TracingService.InvocationTraceData>;
41
+
42
+ traceInvocationEnd({
43
+ trace,
44
+ exception,
45
+ }: {
46
+ trace: TracingService.InvocationTraceData;
47
+ exception?: any;
48
+ }): Effect.Effect<void>;
31
49
  }
32
50
  >() {
33
51
  static noop: Context.Tag.Service<TracingService> = {
34
52
  getTraceContext: () => ({}),
35
53
  write: () => {},
54
+ traceInvocationStart: () =>
55
+ Effect.sync(() => ({ invocationId: ObjectId.random(), invocationTraceQueue: undefined })),
56
+ traceInvocationEnd: () => Effect.sync(() => {}),
36
57
  };
37
58
 
38
59
  static layerNoop: Layer.Layer<TracingService> = Layer.succeed(TracingService, TracingService.noop);
60
+
39
61
  /**
40
62
  * Creates a TracingService layer that emits events to the parent tracing service.
41
63
  */
@@ -46,12 +68,25 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
46
68
  const tracing = yield* TracingService;
47
69
  const context = mapContext(tracing.getTraceContext());
48
70
  return {
49
- write: (event) => tracing.write(event),
71
+ write: (event, context) => tracing.write(event, context),
50
72
  getTraceContext: () => context,
73
+ traceInvocationStart: () => Effect.die('Tracing invocation inside another invocation is not supported.'),
74
+ traceInvocationEnd: () => Effect.die('Tracing invocation inside another invocation is not supported.'),
51
75
  };
52
76
  }),
53
77
  );
54
78
 
79
+ /**
80
+ * Create sublayer to trace an invocation.
81
+ * @param data
82
+ * @returns
83
+ */
84
+ static layerInvocation = (data: TracingService.InvocationTraceData) =>
85
+ TracingService.layerSubframe((context) => ({
86
+ ...context,
87
+ currentInvocation: data,
88
+ }));
89
+
55
90
  /**
56
91
  * Emit the current human-readable execution status.
57
92
  */
@@ -66,6 +101,7 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
66
101
  created: new Date().toISOString(),
67
102
  ...data,
68
103
  }),
104
+ tracing.getTraceContext(),
69
105
  );
70
106
  });
71
107
 
@@ -82,12 +118,15 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
82
118
  ...data.properties,
83
119
  },
84
120
  }),
121
+ tracing.getTraceContext(),
85
122
  );
86
123
  });
87
124
  }
88
125
 
89
126
  export namespace TracingService {
90
127
  export interface TraceContext {
128
+ currentInvocation?: InvocationTraceData;
129
+
91
130
  /**
92
131
  * If this thread sprung from a tool call, this is the ID of the message containing the tool call.
93
132
  */
@@ -100,6 +139,26 @@ export namespace TracingService {
100
139
 
101
140
  debugInfo?: unknown;
102
141
  }
142
+
143
+ /**
144
+ * Trace data for a function/trigger invocation.
145
+ */
146
+ export interface InvocationTraceData {
147
+ invocationId: ObjectId;
148
+ invocationTraceQueue?: DXN.String;
149
+ }
150
+
151
+ /**
152
+ * Payload for a function/trigger invocation.
153
+ */
154
+ export interface FunctionInvocationPayload {
155
+ data?: any;
156
+ inputNodeId?: string;
157
+ trigger?: {
158
+ id: string;
159
+ kind: Trigger.Kind;
160
+ };
161
+ }
103
162
  }
104
163
 
105
164
  /**
@@ -50,7 +50,7 @@ export const Function = Schema.Struct({
50
50
  // Local binding to a function name.
51
51
  binding: Schema.optional(Schema.String),
52
52
  }).pipe(
53
- Type.Obj({
53
+ Type.object({
54
54
  typename: 'dxos.org/type/Function',
55
55
  version: '0.1.0',
56
56
  }),
@@ -68,13 +68,15 @@ export const make = (props: Obj.MakeProps<typeof Function>) => Obj.make(Function
68
68
  * @param source - Source object to copy properties from.
69
69
  */
70
70
  export const setFrom = (target: Function, source: Function) => {
71
- target.key = source.key ?? target.key;
72
- target.name = source.name ?? target.name;
73
- target.version = source.version;
74
- target.description = source.description;
75
- target.updated = source.updated;
76
- // TODO(dmaretskyi): A workaround for an ECHO bug.
77
- target.inputSchema = source.inputSchema ? JSON.parse(JSON.stringify(source.inputSchema)) : undefined;
78
- target.outputSchema = source.outputSchema ? JSON.parse(JSON.stringify(source.outputSchema)) : undefined;
79
- Obj.getMeta(target).keys = JSON.parse(JSON.stringify(Obj.getMeta(source).keys));
71
+ Obj.change(target, (t) => {
72
+ t.key = source.key ?? target.key;
73
+ t.name = source.name ?? target.name;
74
+ t.version = source.version;
75
+ t.description = source.description;
76
+ t.updated = source.updated;
77
+ // TODO(dmaretskyi): A workaround for an ECHO bug.
78
+ t.inputSchema = source.inputSchema ? JSON.parse(JSON.stringify(source.inputSchema)) : undefined;
79
+ t.outputSchema = source.outputSchema ? JSON.parse(JSON.stringify(source.outputSchema)) : undefined;
80
+ Obj.getMeta(t).keys = JSON.parse(JSON.stringify(Obj.getMeta(source).keys));
81
+ });
80
82
  };
@@ -19,15 +19,16 @@ export const Script = Schema.Struct({
19
19
  changed: Schema.Boolean.pipe(FormInputAnnotation.set(false), Schema.optional),
20
20
  source: Type.Ref(Text.Text).pipe(FormInputAnnotation.set(false)),
21
21
  }).pipe(
22
- Type.Obj({
22
+ Type.object({
23
23
  typename: 'dxos.org/type/Script',
24
24
  version: '0.1.0',
25
25
  }),
26
26
  Annotation.LabelAnnotation.set(['name']),
27
27
  );
28
+
28
29
  export interface Script extends Schema.Schema.Type<typeof Script> {}
29
30
 
30
31
  type Props = Omit<Obj.MakeProps<typeof Script>, 'source'> & { source?: string };
31
32
 
32
- export const make = ({ source = '', ...props }: Props = {}) =>
33
+ export const make = ({ source = '', ...props }: Props = {}): Script =>
33
34
  Obj.make(Script, { ...props, source: Ref.make(Text.make(source)) });
@@ -8,6 +8,7 @@ import * as SchemaAST from 'effect/SchemaAST';
8
8
  import { Obj, QueryAST, Type } from '@dxos/echo';
9
9
  import { OptionsAnnotationId, SystemTypeAnnotation } from '@dxos/echo/internal';
10
10
  import { DXN } from '@dxos/keys';
11
+ import { Expando } from '@dxos/schema';
11
12
 
12
13
  /**
13
14
  * Type discriminator for TriggerType.
@@ -96,12 +97,12 @@ export type Spec = Schema.Schema.Type<typeof Spec>;
96
97
  * Function is invoked with the `payload` passed as input data.
97
98
  * The event that triggers the function is available in the function context.
98
99
  */
99
- const Trigger_ = Schema.Struct({
100
+ const TriggerSchema = Schema.Struct({
100
101
  /**
101
102
  * Function or workflow to invoke.
102
103
  */
103
104
  // TODO(dmaretskyi): Can be a Ref(FunctionType) or Ref(ComputeGraphType).
104
- function: Schema.optional(Type.Ref(Type.Expando).annotations({ title: 'Function' })),
105
+ function: Schema.optional(Type.Ref(Expando.Expando).annotations({ title: 'Function' })),
105
106
 
106
107
  /**
107
108
  * Only used for workflowSchema.
@@ -110,6 +111,8 @@ const Trigger_ = Schema.Struct({
110
111
  */
111
112
  inputNodeId: Schema.optional(Schema.String.annotations({ title: 'Input Node ID' })),
112
113
 
114
+ // TODO(burdon): NO BOOLEAN PROPERTIES (enabld/disabled/paused, etc.)
115
+ // Need lint rule; or agent rule to require PR review for "boolean" key word.
113
116
  enabled: Schema.optional(Schema.Boolean.annotations({ title: 'Enabled' })),
114
117
 
115
118
  spec: Schema.optional(Spec),
@@ -127,14 +130,14 @@ const Trigger_ = Schema.Struct({
127
130
  */
128
131
  input: Schema.optional(Schema.mutable(Schema.Record({ key: Schema.String, value: Schema.Any }))),
129
132
  }).pipe(
130
- Type.Obj({
133
+ Type.object({
131
134
  typename: 'dxos.org/type/Trigger',
132
135
  version: '0.1.0',
133
136
  }),
134
137
  SystemTypeAnnotation.set(true),
135
138
  );
136
- export interface Trigger extends Schema.Schema.Type<typeof Trigger_> {}
137
- export interface TriggerEncoded extends Schema.Schema.Encoded<typeof Trigger_> {}
138
- export const Trigger: Schema.Schema<Trigger, TriggerEncoded> = Trigger_;
139
+
140
+ export interface Trigger extends Schema.Schema.Type<typeof TriggerSchema> {}
141
+ export const Trigger: Type.Obj<Trigger> = TriggerSchema as any;
139
142
 
140
143
  export const make = (props: Obj.MakeProps<typeof Trigger>) => Obj.make(Trigger, props);
@@ -4,9 +4,12 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { DXN, Obj, Type } from '@dxos/echo';
7
+ import { DXN, Type } from '@dxos/echo';
8
8
 
9
- export type TriggerEvent = EmailEvent | QueueEvent | SubscriptionEvent | TimerEvent | WebhookEvent;
9
+ // TODO(wittjosiah): Review this type.
10
+ // - Should be discriminated union.
11
+ // - Should be more consistent (e.g. subject vs item).
12
+ // - Should re-use schemas if possible.
10
13
 
11
14
  // TODO(burdon): Reuse trigger schema from @dxos/functions (TriggerType).
12
15
  export const EmailEvent = Schema.mutable(
@@ -39,7 +42,7 @@ export const SubscriptionEvent = Schema.Struct({
39
42
  /**
40
43
  * Reference to the object that was changed or created.
41
44
  */
42
- subject: Type.Ref(Obj.Any),
45
+ subject: Type.Ref(Type.Obj),
43
46
 
44
47
  /**
45
48
  * @deprecated
@@ -60,3 +63,6 @@ export const WebhookEvent = Schema.mutable(
60
63
  }),
61
64
  );
62
65
  export type WebhookEvent = Schema.Schema.Type<typeof WebhookEvent>;
66
+
67
+ export const TriggerEvent = Schema.Union(EmailEvent, QueueEvent, SubscriptionEvent, TimerEvent, WebhookEvent);
68
+ export type TriggerEvent = Schema.Schema.Type<typeof TriggerEvent>;
package/src/types/url.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type ObjectMeta } from '@dxos/echo/internal';
5
+ import { type Obj } from '@dxos/echo';
6
6
 
7
7
  // TODO: use URL scheme for source?
8
8
  export const FUNCTIONS_META_KEY = 'dxos.org/service/function';
@@ -12,14 +12,15 @@ export const FUNCTIONS_PRESET_META_KEY = 'dxos.org/service/function-preset';
12
12
  /**
13
13
  * NOTE: functionId is backend ID, not ECHO object id.
14
14
  */
15
- export const getUserFunctionIdInMetadata = (meta: ObjectMeta) => {
15
+ export const getUserFunctionIdInMetadata = (meta: Obj.ReadonlyMeta) => {
16
16
  return meta.keys.find((key) => key.source === FUNCTIONS_META_KEY)?.id;
17
17
  };
18
18
 
19
19
  /**
20
20
  * NOTE: functionId is backend ID, not ECHO object id.
21
+ * Must be called inside Obj.changeMeta() since it mutates the meta.
21
22
  */
22
- export const setUserFunctionIdInMetadata = (meta: ObjectMeta, functionId: string) => {
23
+ export const setUserFunctionIdInMetadata = (meta: Obj.Meta, functionId: string) => {
23
24
  const key = meta.keys.find((key) => key.source === FUNCTIONS_META_KEY);
24
25
  if (key) {
25
26
  if (key.id !== functionId) {