@outputai/core 0.8.1-next.e92f632.0 → 0.8.2-dev.e78f6b4.0

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 (102) hide show
  1. package/package.json +9 -10
  2. package/src/bus.js +1 -1
  3. package/src/consts.js +5 -2
  4. package/src/helpers/component.js +12 -0
  5. package/src/helpers/component.spec.js +54 -0
  6. package/src/helpers/fetch.js +105 -0
  7. package/src/helpers/fetch.spec.js +203 -0
  8. package/src/helpers/function.js +15 -0
  9. package/src/helpers/function.spec.js +48 -0
  10. package/src/helpers/object.js +98 -0
  11. package/src/helpers/object.spec.js +377 -0
  12. package/src/helpers/promise.js +29 -0
  13. package/src/helpers/promise.spec.js +35 -0
  14. package/src/helpers/string.js +30 -0
  15. package/src/helpers/string.spec.js +64 -0
  16. package/src/interface/evaluator.js +14 -12
  17. package/src/interface/evaluator.spec.js +10 -6
  18. package/src/interface/index.d.ts +7 -6
  19. package/src/interface/index.js +2 -0
  20. package/src/interface/logger.d.ts +53 -0
  21. package/src/interface/logger.js +68 -0
  22. package/src/interface/logger.spec.js +138 -0
  23. package/src/interface/step.js +15 -12
  24. package/src/interface/step.spec.js +10 -6
  25. package/src/interface/webhook.d.ts +21 -2
  26. package/src/interface/workflow.js +85 -78
  27. package/src/interface/workflow.spec.js +11 -4
  28. package/src/internal_activities/index.js +38 -35
  29. package/src/internal_activities/index.spec.js +27 -4
  30. package/src/logger/development.js +2 -2
  31. package/src/logger/development.spec.js +19 -2
  32. package/src/logger/production.js +1 -1
  33. package/src/logger/production.spec.js +24 -5
  34. package/src/sdk/README.md +47 -0
  35. package/src/sdk/helpers/component_metadata.d.ts +17 -0
  36. package/src/sdk/helpers/component_metadata.js +6 -0
  37. package/src/sdk/helpers/component_metadata.spec.js +30 -0
  38. package/src/sdk/helpers/index.d.ts +12 -0
  39. package/src/sdk/helpers/index.js +3 -0
  40. package/src/sdk/helpers/objects.d.ts +51 -0
  41. package/src/sdk/helpers/objects.js +8 -0
  42. package/src/sdk/helpers/objects.spec.js +16 -0
  43. package/src/sdk/helpers/path.d.ts +11 -0
  44. package/src/sdk/helpers/path.js +32 -0
  45. package/src/{utils/resolve_invocation_dir.spec.js → sdk/helpers/path.spec.js} +9 -9
  46. package/src/sdk/runtime/context.d.ts +30 -0
  47. package/src/sdk/runtime/context.js +15 -0
  48. package/src/{activity_integration → sdk/runtime}/context.spec.js +5 -5
  49. package/src/sdk/runtime/events.d.ts +15 -0
  50. package/src/sdk/runtime/events.js +18 -0
  51. package/src/{activity_integration → sdk/runtime}/events.spec.js +8 -9
  52. package/src/sdk/runtime/index.d.ts +12 -0
  53. package/src/sdk/runtime/index.js +3 -0
  54. package/src/sdk/runtime/tracing.d.ts +46 -0
  55. package/src/sdk/runtime/tracing.js +11 -0
  56. package/src/tracing/processors/s3/redis_client.spec.js +0 -6
  57. package/src/tracing/processors/s3/s3_client.spec.js +0 -6
  58. package/src/tracing/trace_engine.js +1 -1
  59. package/src/worker/catalog_workflow/catalog_job.js +1 -1
  60. package/src/worker/catalog_workflow/index.spec.js +8 -11
  61. package/src/worker/configs.js +14 -1
  62. package/src/worker/configs.spec.js +34 -1
  63. package/src/worker/connection_monitor.js +3 -14
  64. package/src/worker/connection_monitor.spec.js +4 -21
  65. package/src/worker/global_functions.js +14 -0
  66. package/src/worker/global_functions.spec.js +55 -0
  67. package/src/worker/index.js +11 -3
  68. package/src/worker/index.spec.js +29 -1
  69. package/src/worker/interceptors/activity.js +2 -2
  70. package/src/worker/interceptors/workflow.js +3 -3
  71. package/src/worker/interceptors/workflow.spec.js +1 -1
  72. package/src/worker/loader/matchers.js +1 -1
  73. package/src/worker/loader/tools.js +1 -1
  74. package/src/worker/loader/tools.spec.js +1 -1
  75. package/src/worker/log_hooks.js +14 -0
  76. package/src/worker/log_hooks.spec.js +83 -2
  77. package/src/worker/sinks.js +7 -1
  78. package/src/worker/sinks.spec.js +203 -0
  79. package/src/activity_integration/context.d.ts +0 -23
  80. package/src/activity_integration/context.js +0 -18
  81. package/src/activity_integration/event_id_integration.spec.js +0 -52
  82. package/src/activity_integration/events.d.ts +0 -10
  83. package/src/activity_integration/events.js +0 -15
  84. package/src/activity_integration/index.d.ts +0 -9
  85. package/src/activity_integration/index.js +0 -3
  86. package/src/activity_integration/tracing.d.ts +0 -40
  87. package/src/activity_integration/tracing.js +0 -48
  88. package/src/utils/index.d.ts +0 -180
  89. package/src/utils/index.js +0 -2
  90. package/src/utils/resolve_invocation_dir.js +0 -34
  91. package/src/utils/utils.js +0 -334
  92. package/src/utils/utils.spec.js +0 -723
  93. /package/src/{internal_utils → helpers}/aggregations.js +0 -0
  94. /package/src/{internal_utils → helpers}/aggregations.spec.js +0 -0
  95. /package/src/{internal_utils → helpers}/errors.js +0 -0
  96. /package/src/{internal_utils → helpers}/errors.spec.js +0 -0
  97. /package/src/{internal_utils → helpers}/temporal_context.js +0 -0
  98. /package/src/{internal_utils → helpers}/temporal_context.spec.ts +0 -0
  99. /package/src/{internal_utils → helpers}/trace_info.js +0 -0
  100. /package/src/{internal_utils → helpers}/trace_info.spec.js +0 -0
  101. /package/src/{internal_utils → helpers}/workflow_context.js +0 -0
  102. /package/src/{internal_utils → helpers}/workflow_context.spec.js +0 -0
@@ -1,9 +1,10 @@
1
1
  import { FatalError } from '#errors';
2
2
  import { fetch } from 'undici';
3
- import { setMetadata, serializeFetchResponse, serializeBodyAndInferContentType } from '#utils';
4
- import { ComponentType } from '#consts';
3
+ import { serializeFetchResponse, serializeBodyAndInferContentType } from '#helpers/fetch';
5
4
  import { createChildLogger } from '#logger';
6
5
  import { getDestinations } from '#tracing';
6
+ import { createInternalStep } from '#helpers/component';
7
+ import { ACTIVITY_GET_TRACE_DESTINATIONS, ACTIVITY_SEND_HTTP_REQUEST } from '#consts';
7
8
 
8
9
  const log = createChildLogger( 'HttpClient' );
9
10
 
@@ -19,41 +20,42 @@ const log = createChildLogger( 'HttpClient' );
19
20
  * @returns {object} The serialized HTTP response
20
21
  * @throws {FatalError}
21
22
  */
22
- export const sendHttpRequest = async ( { url, method, payload = undefined, headers = undefined, timeout = 30_000 } ) => {
23
- const args = {
24
- method,
25
- headers: new Headers( headers ?? {} ),
26
- signal: AbortSignal.timeout( timeout )
27
- };
23
+ export const sendHttpRequest = createInternalStep( {
24
+ name: ACTIVITY_SEND_HTTP_REQUEST,
25
+ handler: async ( { url, method, payload = undefined, headers = undefined, timeout = 30_000 } ) => {
26
+ const args = {
27
+ method,
28
+ headers: new Headers( headers ?? {} ),
29
+ signal: AbortSignal.timeout( timeout )
30
+ };
28
31
 
29
- const methodsWithBody = [ 'DELETE', 'PATCH', 'POST', 'PUT', 'OPTIONS' ];
30
- const hasBodyPayload = ![ undefined, null ].includes( payload );
31
- if ( methodsWithBody.includes( method ) && hasBodyPayload ) {
32
- const { body, contentType } = serializeBodyAndInferContentType( payload );
33
- if ( contentType && !args.headers.has( 'content-type' ) ) {
34
- args.headers.set( 'Content-Type', contentType );
35
- }
36
- Object.assign( args, { body } );
37
- };
38
-
39
- const response = await ( async () => {
40
- try {
41
- return await fetch( url, args );
42
- } catch ( e ) {
43
- throw new FatalError( `${method} ${url} ${e.cause ?? e.message}` );
44
- }
45
- } )();
32
+ const methodsWithBody = [ 'DELETE', 'PATCH', 'POST', 'PUT', 'OPTIONS' ];
33
+ const hasBodyPayload = ![ undefined, null ].includes( payload );
34
+ if ( methodsWithBody.includes( method ) && hasBodyPayload ) {
35
+ const { body, contentType } = serializeBodyAndInferContentType( payload );
36
+ if ( contentType && !args.headers.has( 'content-type' ) ) {
37
+ args.headers.set( 'Content-Type', contentType );
38
+ }
39
+ Object.assign( args, { body } );
40
+ };
46
41
 
47
- log.info( 'HTTP request completed', { url, method, status: response.status, statusText: response.statusText } );
42
+ const response = await ( async () => {
43
+ try {
44
+ return await fetch( url, args );
45
+ } catch ( e ) {
46
+ throw new FatalError( `${method} ${url} ${e.cause ?? e.message}` );
47
+ }
48
+ } )();
48
49
 
49
- if ( !response.ok ) {
50
- throw new FatalError( `${method} ${url} ${response.status}` );
51
- }
50
+ log.info( 'HTTP request completed', { url, method, status: response.status, statusText: response.statusText } );
52
51
 
53
- return serializeFetchResponse( response );
54
- };
52
+ if ( !response.ok ) {
53
+ throw new FatalError( `${method} ${url} ${response.status}` );
54
+ }
55
55
 
56
- setMetadata( sendHttpRequest, { type: ComponentType.INTERNAL_STEP } );
56
+ return serializeFetchResponse( response );
57
+ }
58
+ } );
57
59
 
58
60
  /**
59
61
  * Invokes a trace method that resolves all trace output paths based on the traceInfo
@@ -61,6 +63,7 @@ setMetadata( sendHttpRequest, { type: ComponentType.INTERNAL_STEP } );
61
63
  * @param {object} traceInfo
62
64
  * @returns {object} Information about enabled destinations
63
65
  */
64
- export const getTraceDestinations = traceInfo => getDestinations( traceInfo );
65
-
66
- setMetadata( getTraceDestinations, { type: ComponentType.INTERNAL_STEP } );
66
+ export const getTraceDestinations = createInternalStep( {
67
+ name: ACTIVITY_GET_TRACE_DESTINATIONS,
68
+ handler: traceInfo => getDestinations( traceInfo )
69
+ } );
@@ -1,10 +1,12 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { MockAgent, setGlobalDispatcher } from 'undici';
3
3
  import { FatalError } from '#errors';
4
- import { serializeBodyAndInferContentType, serializeFetchResponse } from '#utils';
4
+ import { ACTIVITY_GET_TRACE_DESTINATIONS, ACTIVITY_SEND_HTTP_REQUEST } from '#consts';
5
+ import { serializeBodyAndInferContentType, serializeFetchResponse } from '#helpers/fetch';
5
6
  import { getTraceDestinations, sendHttpRequest } from './index.js';
6
7
 
7
8
  const getDestinationsMock = vi.hoisted( () => vi.fn() );
9
+ const createInternalStepMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
8
10
 
9
11
  vi.mock( '#tracing', () => ( {
10
12
  getDestinations: getDestinationsMock
@@ -15,9 +17,15 @@ vi.mock( '#logger', () => {
15
17
  return { createChildLogger: vi.fn( () => log ) };
16
18
  } );
17
19
 
18
- vi.mock( '#utils', () => ( {
19
- setMetadata: vi.fn(),
20
- isStringboolTrue: vi.fn( () => false ),
20
+ vi.mock( '#helpers/component', () => ( {
21
+ createInternalStep: createInternalStepMock
22
+ } ) );
23
+
24
+ vi.mock( '#helpers/string', () => ( {
25
+ isStringboolTrue: vi.fn( () => false )
26
+ } ) );
27
+
28
+ vi.mock( '#helpers/fetch', () => ( {
21
29
  serializeBodyAndInferContentType: vi.fn(),
22
30
  serializeFetchResponse: vi.fn()
23
31
  } ) );
@@ -30,6 +38,21 @@ setGlobalDispatcher( mockAgent );
30
38
  const url = 'https://growthx.ai';
31
39
  const method = 'GET';
32
40
 
41
+ describe( 'internal_activities component registration', () => {
42
+ it( 'creates internal step components for exported activities', () => {
43
+ expect( createInternalStepMock ).toHaveBeenNthCalledWith( 1, {
44
+ name: ACTIVITY_SEND_HTTP_REQUEST,
45
+ handler: expect.any( Function )
46
+ } );
47
+ expect( createInternalStepMock ).toHaveBeenNthCalledWith( 2, {
48
+ name: ACTIVITY_GET_TRACE_DESTINATIONS,
49
+ handler: expect.any( Function )
50
+ } );
51
+ expect( sendHttpRequest ).toBe( createInternalStepMock.mock.calls[0][0].handler );
52
+ expect( getTraceDestinations ).toBe( createInternalStepMock.mock.calls[1][0].handler );
53
+ } );
54
+ } );
55
+
33
56
  describe( 'internal_activities/sendHttpRequest', () => {
34
57
  beforeEach( async () => {
35
58
  vi.restoreAllMocks();
@@ -1,5 +1,5 @@
1
1
  import { format, transports } from 'winston';
2
- import { isPlainObject, shuffleArray } from '#utils';
2
+ import { shuffleArray, isPlainObject } from '#helpers/object';
3
3
 
4
4
  /** Available colors enum */
5
5
  const Color = {
@@ -47,7 +47,7 @@ const getColor = v =>
47
47
  assignedColors.get( v ) ?? assignedColors.set( v, COLORS[assignedColors.size % COLORS.length] ).get( v );
48
48
 
49
49
  export const options = {
50
- level: 'debug',
50
+ level: process.env.OUTPUT_LOG_LEVEL ?? 'debug',
51
51
  transports: [ new transports.Console() ],
52
52
  format: format.combine(
53
53
  format.colorize(),
@@ -1,9 +1,9 @@
1
- import { describe, expect, it, vi } from 'vitest';
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  const LEVEL = Symbol.for( 'level' );
4
4
  const MESSAGE = Symbol.for( 'message' );
5
5
 
6
- vi.mock( '#utils', () => ( {
6
+ vi.mock( '#helpers/object', () => ( {
7
7
  isPlainObject: v => Object.prototype.toString.call( v ) === '[object Object]',
8
8
  shuffleArray: v => v
9
9
  } ) );
@@ -14,6 +14,10 @@ const loadDevelopmentLogger = async () => {
14
14
  };
15
15
 
16
16
  describe( 'logger/development', () => {
17
+ afterEach( () => {
18
+ vi.unstubAllEnvs();
19
+ } );
20
+
17
21
  describe( 'formatJson', () => {
18
22
  it( 'formats nested plain objects and arrays', async () => {
19
23
  const { formatJson } = await loadDevelopmentLogger();
@@ -40,6 +44,19 @@ describe( 'logger/development', () => {
40
44
  } );
41
45
 
42
46
  describe( 'options.format', () => {
47
+ it( 'uses debug level by default', async () => {
48
+ const { options } = await loadDevelopmentLogger();
49
+
50
+ expect( options.level ).toBe( 'debug' );
51
+ } );
52
+
53
+ it( 'uses OUTPUT_LOG_LEVEL when configured', async () => {
54
+ vi.stubEnv( 'OUTPUT_LOG_LEVEL', 'http' );
55
+ const { options } = await loadDevelopmentLogger();
56
+
57
+ expect( options.level ).toBe( 'http' );
58
+ } );
59
+
43
60
  it( 'formats level, namespace, message, and metadata fields', async () => {
44
61
  const { options } = await loadDevelopmentLogger();
45
62
  const info = options.format.transform( {
@@ -1,7 +1,7 @@
1
1
  import { transports, format } from 'winston';
2
2
 
3
3
  export const options = {
4
- level: 'info',
4
+ level: process.env.OUTPUT_LOG_LEVEL ?? 'info',
5
5
  transports: [ new transports.Console() ],
6
6
  defaultMeta: {
7
7
  service: 'output-worker',
@@ -1,11 +1,21 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { options } from './production.js';
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
2
 
4
3
  const LEVEL = Symbol.for( 'level' );
5
4
  const MESSAGE = Symbol.for( 'message' );
6
5
 
6
+ const loadProductionLogger = async () => {
7
+ vi.resetModules();
8
+ return import( './production.js' );
9
+ };
10
+
7
11
  describe( 'logger/production', () => {
8
- it( 'uses info level and default production metadata', () => {
12
+ afterEach( () => {
13
+ vi.unstubAllEnvs();
14
+ } );
15
+
16
+ it( 'uses info level and default production metadata', async () => {
17
+ const { options } = await loadProductionLogger();
18
+
9
19
  expect( options.level ).toBe( 'info' );
10
20
  expect( options.defaultMeta ).toEqual( {
11
21
  service: 'output-worker',
@@ -13,7 +23,15 @@ describe( 'logger/production', () => {
13
23
  } );
14
24
  } );
15
25
 
16
- it( 'formats logs as JSON with timestamp and metadata fields', () => {
26
+ it( 'uses OUTPUT_LOG_LEVEL when configured', async () => {
27
+ vi.stubEnv( 'OUTPUT_LOG_LEVEL', 'debug' );
28
+ const { options } = await loadProductionLogger();
29
+
30
+ expect( options.level ).toBe( 'debug' );
31
+ } );
32
+
33
+ it( 'formats logs as JSON with timestamp and metadata fields', async () => {
34
+ const { options } = await loadProductionLogger();
17
35
  const info = options.format.transform( {
18
36
  [LEVEL]: 'info',
19
37
  level: 'info',
@@ -33,7 +51,8 @@ describe( 'logger/production', () => {
33
51
  expect( output.timestamp ).toEqual( expect.stringMatching( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[+-]\d{2}:\d{2}$/u ) );
34
52
  } );
35
53
 
36
- it( 'includes error stack when formatting Error messages', () => {
54
+ it( 'includes error stack when formatting Error messages', async () => {
55
+ const { options } = await loadProductionLogger();
37
56
  const error = new Error( 'boom' );
38
57
  const info = options.format.transform( {
39
58
  [LEVEL]: 'error',
@@ -0,0 +1,47 @@
1
+ ## SDK integration API for Output packages
2
+
3
+ **THESE MODULES ARE NOT PUBLIC. DO NOT IMPORT THEM ON YOUR PROJECT.**
4
+
5
+ This folder contains integration helpers used by other Output SDK packages.
6
+
7
+ The two subfolders represent a hard Temporal boundary. Code imported by
8
+ workflow-bundled modules must only depend on `helpers`. Code that needs worker
9
+ or activity runtime state belongs in `runtime`.
10
+
11
+ Do not change these without reviewing call-sites.
12
+
13
+ ### `helpers`
14
+
15
+ Helpers must be safe to import while Temporal workflows are bundled and executed
16
+ in the workflow sandbox.
17
+
18
+ Allowed:
19
+
20
+ - Stateless functions and namespace objects.
21
+ - Deterministic code with no runtime side effects at import time.
22
+ - Imports from other sandbox-safe helper modules.
23
+ - Small, dependency-free helpers for any deterministic computation that can run
24
+ without worker or activity state.
25
+
26
+ Not allowed:
27
+
28
+ - Access to activity context, async storage, sinks, tracing state, workers, or
29
+ clients.
30
+ - Imports from `@temporalio/activity`, `@temporalio/worker`, or Node-only APIs
31
+ that cannot be bundled into workflow code.
32
+ - Process, filesystem, network, clock, randomness, or environment access unless
33
+ the behavior is known to be safe and deterministic in the workflow sandbox.
34
+ - Any import chain that reaches `runtime`.
35
+
36
+ If a helper cannot satisfy these rules, put it in `runtime` instead.
37
+
38
+ ### `runtime`
39
+
40
+ Runtime modules are for helpers that need Output or Temporal runtime state.
41
+
42
+ Use `runtime` for activity-only integrations such as context lookup, event
43
+ emission, tracing, async storage, worker state, sinks, and other behavior that is
44
+ not safe inside workflow-bundled code.
45
+
46
+ Runtime modules may depend on activity runtime APIs and internal state, but they
47
+ must not be imported by workflow-safe code.
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tools to extract data from Outputai Core Components metadata storage
3
+ */
4
+ export declare const ComponentMetadata: {
5
+ /**
6
+ * Extract the name from a workflow(), step() or evaluator() return
7
+ * @param fn
8
+ * @returns
9
+ */
10
+ getName( fn: Function ): string | undefined;
11
+
12
+ /**
13
+ * Check if a function has metadata from workflow(), step() or evaluator()
14
+ * @param fn
15
+ */
16
+ has( fn: Function ): boolean;
17
+ };
@@ -0,0 +1,6 @@
1
+ import { METADATA_ACCESS_SYMBOL } from '#consts';
2
+
3
+ export const ComponentMetadata = {
4
+ getName: fn => fn[METADATA_ACCESS_SYMBOL]?.name,
5
+ has: fn => !!fn[METADATA_ACCESS_SYMBOL]
6
+ };
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { METADATA_ACCESS_SYMBOL } from '#consts';
3
+ import { ComponentMetadata } from './component_metadata.js';
4
+
5
+ describe( 'ComponentMetadata', () => {
6
+ it( 'detects functions tagged with component metadata', () => {
7
+ const component = () => {};
8
+ Object.defineProperty( component, METADATA_ACCESS_SYMBOL, {
9
+ value: { name: 'my_component' }
10
+ } );
11
+
12
+ expect( ComponentMetadata.has( component ) ).toBe( true );
13
+ } );
14
+
15
+ it( 'returns the component metadata name', () => {
16
+ const component = () => {};
17
+ Object.defineProperty( component, METADATA_ACCESS_SYMBOL, {
18
+ value: { name: 'my_component' }
19
+ } );
20
+
21
+ expect( ComponentMetadata.getName( component ) ).toBe( 'my_component' );
22
+ } );
23
+
24
+ it( 'returns false and undefined for untagged functions', () => {
25
+ const fn = () => {};
26
+
27
+ expect( ComponentMetadata.has( fn ) ).toBe( false );
28
+ expect( ComponentMetadata.getName( fn ) ).toBeUndefined();
29
+ } );
30
+ } );
@@ -0,0 +1,12 @@
1
+ /**
2
+ * > [!WARNING]
3
+ * > **Internal use only.** Not part of the public API; may change without notice.
4
+ *
5
+ * These are stateless helpers for other SDK modules integration.
6
+ * They can be used while Temporal workflows are bundled.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ export * from './component_metadata.js';
11
+ export * from './path.js';
12
+ export * from './objects.js';
@@ -0,0 +1,3 @@
1
+ export * from './component_metadata.js';
2
+ export * from './path.js';
3
+ export * from './objects.js';
@@ -0,0 +1,51 @@
1
+ /** Tools to manipulate JS Objects */
2
+ export declare const Objects: {
3
+ /**
4
+ * Node safe clone implementation that doesn't use global structuredClone().
5
+ *
6
+ * Returns a cloned version of the object.
7
+ *
8
+ * Only clones static properties. Getters become static properties.
9
+ *
10
+ * @param object
11
+ */
12
+ clone( object: object ): object,
13
+
14
+ /**
15
+ * Returns true if the value is a plain object:
16
+ * - `{}`
17
+ * - `new Object()`
18
+ * - `Object.create(null)`
19
+ *
20
+ * @param object - The value to check.
21
+ * @returns Whether the value is a plain object.
22
+ */
23
+ isPlainObject( object: unknown ): boolean,
24
+
25
+ /**
26
+ * Creates a new object by merging object `b` onto object `a`, biased toward `b`:
27
+ * - Fields in `b` overwrite fields in `a`.
28
+ * - Fields in `b` that don't exist in `a` are created.
29
+ * - Fields in `a` that don't exist in `b` are left unchanged.
30
+ *
31
+ * @param a - The base object.
32
+ * @param b - The overriding object.
33
+ * @throws {Error} If either `a` or `b` is not a plain object.
34
+ * @returns A new merged object.
35
+ */
36
+ deepMerge( a: object, b: object | null | undefined ): object,
37
+
38
+ /**
39
+ * Creates a new object by merging object `b` onto object `a`, biased toward `b`:
40
+ * - Fields in `b` that don't exist in `a` are created.
41
+ * - Fields in `a` that don't exist in `b` are left unchanged.
42
+ * - Fields in `a` and `b` are passed as arguments to the resolve function (a,b) and its return assigns the new value.
43
+ *
44
+ * @param a - The base object.
45
+ * @param b - The overriding object.
46
+ * @param resolver - The resolver function.
47
+ * @throws {Error} If either `a` or `b` is not a plain object.
48
+ * @returns A new merged object.
49
+ */
50
+ deepMergeWithResolver( a: object, b: object | null | undefined, resolver: ( a: unknown, b: unknown ) => unknown ): object
51
+ };
@@ -0,0 +1,8 @@
1
+ import { clone, deepMerge, deepMergeWithResolver, isPlainObject } from '#helpers/object';
2
+
3
+ export const Objects = {
4
+ clone,
5
+ deepMerge,
6
+ deepMergeWithResolver,
7
+ isPlainObject
8
+ };
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Objects } from './objects.js';
3
+ import { Objects as ObjectsFromIndex } from './index.js';
4
+ import { clone, deepMerge, deepMergeWithResolver, isPlainObject } from '#helpers/object';
5
+
6
+ describe( 'Objects', () => {
7
+ it( 'exports the same functions from the object helper module', () => {
8
+ expect( Objects ).toBe( ObjectsFromIndex );
9
+ expect( Objects ).toEqual( {
10
+ clone,
11
+ deepMerge,
12
+ deepMergeWithResolver,
13
+ isPlainObject
14
+ } );
15
+ } );
16
+ } );
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tools to interact with filesystem paths
3
+ */
4
+ export declare const Path: {
5
+ /**
6
+ * Return the first immediate directory of the file invoking the code that called this function.
7
+ *
8
+ * Excludes `@outputai/core`, node, other internal paths, and any additional ignore paths.
9
+ */
10
+ resolveInvocationDir( additionalIgnorePaths?: string[] ): string
11
+ };
@@ -0,0 +1,32 @@
1
+ import * as stackTraceParser from 'stacktrace-parser';
2
+
3
+ // OS separator, but in a deterministic way, allowing this to work in Temporal's sandbox
4
+ // This avoids importing from node:path
5
+ const SEP = new Error().stack.includes( '/' ) ? '/' : '\\';
6
+ const transformSeparators = path => path.replaceAll( '/', SEP );
7
+
8
+ const defaultIgnorePaths = [
9
+ '/@outputai/core/',
10
+ '/@outputai/llm/',
11
+ '/@outputai/evals/',
12
+ '/sdk/core/',
13
+ '/sdk/llm/',
14
+ '/sdk/evals/',
15
+ 'node:internal/',
16
+ 'evalmachine.',
17
+ 'webpack/bootstrap'
18
+ ];
19
+
20
+ export const Path = {
21
+ resolveInvocationDir: ( additionalIgnorePaths = [] ) => {
22
+ const stack = new Error().stack;
23
+ const lines = stackTraceParser.parse( stack );
24
+ const ignorePaths = [ ...additionalIgnorePaths, ...defaultIgnorePaths ].map( transformSeparators );
25
+
26
+ const frame = lines.find( l => !ignorePaths.some( p => l.file.includes( p ) ) );
27
+ if ( !frame ) {
28
+ throw new Error( `Invocation dir resolution via stack trace failed. Stack: ${stack}` );
29
+ }
30
+ return frame.file.replace( 'file://', '' ).split( SEP ).slice( 0, -1 ).join( SEP );
31
+ }
32
+ };
@@ -1,5 +1,5 @@
1
1
  import { afterEach, describe, expect, it } from 'vitest';
2
- import resolveInvocationDir from './resolve_invocation_dir';
2
+ import { Path } from './path.js';
3
3
 
4
4
  const OriginalError = Error;
5
5
 
@@ -10,7 +10,7 @@ describe( 'Resolve Invocation Dir', () => {
10
10
 
11
11
  it( 'Should detect the invocation dir from the tests workflow', () => {
12
12
  const stack = `Error
13
- at resolveInvocationDir (file:///app/sdk/core/src/utils/resolve_invocation_dir.js)
13
+ at resolveInvocationDir (file:///app/sdk/core/src/sdk/helpers/path.js)
14
14
  at fn (file:///app/test_workflows/dist/simple/steps.js:8:21)
15
15
  at wrapper (file:///app/sdk/core/src/interface/step.js:12:26)
16
16
  at executeNextHandler (/app/node_modules/@temporalio/worker/lib/activity.js:99:54)
@@ -29,12 +29,12 @@ describe( 'Resolve Invocation Dir', () => {
29
29
  }
30
30
  };
31
31
 
32
- expect( resolveInvocationDir() ).toBe( '/app/test_workflows/dist/simple' );
32
+ expect( Path.resolveInvocationDir() ).toBe( '/app/test_workflows/dist/simple' );
33
33
  } );
34
34
 
35
35
  it( 'Should detect the invocation dir from the sandbox environment at sdk/core', () => {
36
36
  const stack = `Error
37
- at resolveInvocationDir (file:///app/sdk/core/src/utils/resolve_invocation_dir.js)
37
+ at resolveInvocationDir (file:///app/sdk/core/src/sdk/helpers/path.js)
38
38
  at workflow (file:///app/sdk/core/src/interface/workflow.js:25:16)
39
39
  at file:///app/test_workflows/dist/nested/workflow.js:4:16
40
40
  at ModuleJob.run (node:internal/modules/esm/module_job:365:25)
@@ -50,12 +50,12 @@ describe( 'Resolve Invocation Dir', () => {
50
50
  }
51
51
  };
52
52
 
53
- expect( resolveInvocationDir() ).toBe( '/app/test_workflows/dist/nested' );
53
+ expect( Path.resolveInvocationDir() ).toBe( '/app/test_workflows/dist/nested' );
54
54
  } );
55
55
 
56
56
  it( 'Should detect the invocation dir from workflow loading at core', () => {
57
57
  const stack = `Error
58
- at __WEBPACK_DEFAULT_EXPORT__ (/app/sdk/core/src/utils/resolve_invocation_dir.js:13:0)
58
+ at resolveInvocationDir (/app/sdk/core/src/sdk/helpers/path.js:21:0)
59
59
  at workflow (/app/sdk/core/src/interface/workflow.js:22:43)
60
60
  at ../../test_workflows/dist/nested/workflow.js (/app/test_workflows/dist/nested/workflow.js:4:24)
61
61
  at __webpack_require__ (webpack/bootstrap:19:0)
@@ -73,12 +73,12 @@ describe( 'Resolve Invocation Dir', () => {
73
73
  }
74
74
  };
75
75
 
76
- expect( resolveInvocationDir() ).toBe( '/app/test_workflows/dist/nested' );
76
+ expect( Path.resolveInvocationDir() ).toBe( '/app/test_workflows/dist/nested' );
77
77
  } );
78
78
 
79
79
  it( 'Should detect the invocation dir from a workflow using core installed via NPM', () => {
80
80
  const stack = `Error
81
- at resolveInvocationDir (file:///app/node_modules/@outputai/core/src/utils/resolve_invocation_dir.js)
81
+ at resolveInvocationDir (file:///app/node_modules/@outputai/core/src/sdk/helpers/path.js)
82
82
  at fn (file:///app/dist/simple/steps.js:8:21)
83
83
  at wrapper (file:///app/node_modules/@outputai/core/src/interface/step.js:12:26)
84
84
  at executeNextHandler (/app/node_modules/@temporalio/worker/lib/activity.js:99:54)
@@ -97,6 +97,6 @@ describe( 'Resolve Invocation Dir', () => {
97
97
  }
98
98
  };
99
99
 
100
- expect( resolveInvocationDir() ).toBe( '/app/dist/simple' );
100
+ expect( Path.resolveInvocationDir() ).toBe( '/app/dist/simple' );
101
101
  } );
102
102
  } );
@@ -0,0 +1,30 @@
1
+ import type { Info } from '@temporalio/activity';
2
+
3
+ /**
4
+ * Context object
5
+ */
6
+ export type Context = {
7
+ /** Temporal info about the current activity */
8
+ activityInfo: Info,
9
+ /** Path of the workflow file */
10
+ workflowFilename: string
11
+ };
12
+
13
+ /**
14
+ * Tools to interact with Runtime context
15
+ */
16
+ export declare const Context: {
17
+
18
+ /**
19
+ * Returns information about the current Temporal execution.
20
+ *
21
+ * Only available when called from within a step or evaluator (Temporal Activities) running in the Temporal runtime.
22
+ *
23
+ * @remarks
24
+ * - Returns `null` when not called inside a Temporal Activity (steps/evaluators);
25
+ * - Returns `null` when not called from within a running Temporal worker, like in unit tests environment;
26
+ *
27
+ * @returns The workflow context, or `null` if unavailable or incomplete.
28
+ */
29
+ getActivityContext(): Context | null;
30
+ };
@@ -0,0 +1,15 @@
1
+ import { Storage } from '#async_storage';
2
+
3
+ export const Context = {
4
+ getActivityContext: () => {
5
+ const ctx = Storage.load();
6
+ if ( !ctx ) {
7
+ return null;
8
+ }
9
+
10
+ return {
11
+ workflowFilename: ctx.workflowFilename,
12
+ activityInfo: ctx.activityInfo
13
+ };
14
+ }
15
+ };