@outputai/core 0.4.1-dev.6555a2c.0 → 0.4.1-dev.7aa9a5f.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/core",
3
- "version": "0.4.1-dev.6555a2c.0",
3
+ "version": "0.4.1-dev.7aa9a5f.0",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,10 +1,5 @@
1
1
  /**
2
- * Emits a custom event on the in-process message bus.
3
- *
4
- * The framework automatically attaches `workflowId`, `runId`, and `activityId`
5
- * (pulled from `executionContext`) onto every emitted payload, so consumer
6
- * subscribers registered via `on(...)` always receive those identifiers
7
- * alongside whatever custom fields the emitter supplies.
2
+ * Emits a custom event
8
3
  *
9
4
  * @param eventName - The name of the event to emit
10
5
  * @param payload - An optional payload to send to the event
@@ -5,6 +5,6 @@ export const emitEvent = ( eventName, payload ) => {
5
5
  const ctx = Storage.load();
6
6
 
7
7
  const { executionContext, parentId: activityId } = ctx ?? {};
8
- const { workflowId, runId } = executionContext ?? {};
9
- messageBus.emit( `external:${eventName}`, { ...payload ?? {}, workflowId, runId, activityId } );
8
+ const { workflowId } = executionContext ?? {};
9
+ messageBus.emit( `external:${eventName}`, { workflowId, activityId, ...payload ?? {} } );
10
10
  };
@@ -16,10 +16,8 @@ export interface ErrorHookPayload {
16
16
  * Payload passed to the onWorkflowStart handler when a workflow run begins.
17
17
  */
18
18
  export interface WorkflowStartHookPayload {
19
- /** Workflow id (stable across retries / continue-as-new). */
19
+ /** Identifier of the workflow run. */
20
20
  id: string;
21
- /** Temporal run id for the current execution attempt. */
22
- runId: string;
23
21
  /** Name of the workflow. */
24
22
  name: string;
25
23
  }
@@ -28,10 +26,8 @@ export interface WorkflowStartHookPayload {
28
26
  * Payload passed to the onWorkflowEnd handler when a workflow run completes successfully.
29
27
  */
30
28
  export interface WorkflowEndHookPayload {
31
- /** Workflow id (stable across retries / continue-as-new). */
29
+ /** Identifier of the workflow run. */
32
30
  id: string;
33
- /** Temporal run id for the current execution attempt. */
34
- runId: string;
35
31
  /** Name of the workflow. */
36
32
  name: string;
37
33
  /** Duration of the workflow run in milliseconds. */
@@ -42,10 +38,8 @@ export interface WorkflowEndHookPayload {
42
38
  * Payload passed to the onWorkflowError handler when a workflow run fails.
43
39
  */
44
40
  export interface WorkflowErrorHookPayload {
45
- /** Workflow id (stable across retries / continue-as-new). */
41
+ /** Identifier of the workflow run. */
46
42
  id: string;
47
- /** Temporal run id for the current execution attempt. */
48
- runId: string;
49
43
  /** Name of the workflow. */
50
44
  name: string;
51
45
  /** Elapsed time before failure in milliseconds. */
@@ -96,37 +90,6 @@ export declare function onWorkflowEnd( handler: ( payload: WorkflowEndHookPayloa
96
90
  */
97
91
  export declare function onWorkflowError( handler: ( payload: WorkflowErrorHookPayload ) => void ): void;
98
92
 
99
- /**
100
- * Payload broadcast on the `http:request` event for every HTTP call issued
101
- * through `@outputai/http`'s `fetch`. Fires for success, non-2xx, and network
102
- * failure paths — `cost:http:request` continues to fire only when the consumer
103
- * has attached a cost via `addRequestCost`.
104
- *
105
- * The framework auto-attaches `workflowId`, `runId`, and `activityId` onto the
106
- * payload before broadcast, so consumers receive those identifiers in addition
107
- * to the fields listed here.
108
- */
109
- export interface HttpRequestHookPayload {
110
- /** Workflow id (stable across retries / continue-as-new). */
111
- workflowId: string;
112
- /** Temporal run id for the current execution attempt. */
113
- runId: string;
114
- /** Activity / step id, when emitted from inside a step. */
115
- activityId?: string;
116
- /** UUID generated per request inside `@outputai/http`. */
117
- requestId: string;
118
- /** HTTP method (uppercase). */
119
- method: string;
120
- /** Absolute request URL. */
121
- url: string;
122
- /** HTTP status code; undefined on network failure. */
123
- status?: number;
124
- /** Elapsed time from request issuance to response (or failure), in milliseconds. */
125
- durationMs: number;
126
- /** Outcome bucket: `success` (2xx-3xx), `http_error` (>=400), `network_error` (DNS / timeout / abort). */
127
- outcome: 'success' | 'http_error' | 'network_error';
128
- }
129
-
130
93
  /**
131
94
  * Register a handler to be invoked when a given event happens
132
95
  *
@@ -34,16 +34,16 @@ export const onBeforeWorkerStart = handler => messageBus.on( BusEventType.WORKER
34
34
  safeInvoke( handler, undefined, 'onBeforeWorkerStart' ) );
35
35
 
36
36
  /** Listen to workflow start events, excludes catalog workflow */
37
- export const onWorkflowStart = handler => messageBus.on( BusEventType.WORKFLOW_START, ( { id, runId, name } ) =>
38
- WORKFLOW_CATALOG !== name ? safeInvoke( handler, { id, runId, name }, 'onWorkflowStart' ) : null );
37
+ export const onWorkflowStart = handler => messageBus.on( BusEventType.WORKFLOW_START, ( { id, name } ) =>
38
+ WORKFLOW_CATALOG !== name ? safeInvoke( handler, { id, name }, 'onWorkflowStart' ) : null );
39
39
 
40
40
  /** Listen to workflow end events, excludes catalog workflow */
41
- export const onWorkflowEnd = handler => messageBus.on( BusEventType.WORKFLOW_END, ( { id, runId, name, duration } ) =>
42
- WORKFLOW_CATALOG !== name ? safeInvoke( handler, { id, runId, name, duration }, 'onWorkflowEnd' ) : null );
41
+ export const onWorkflowEnd = handler => messageBus.on( BusEventType.WORKFLOW_END, ( { id, name, duration } ) =>
42
+ WORKFLOW_CATALOG !== name ? safeInvoke( handler, { id, name, duration }, 'onWorkflowEnd' ) : null );
43
43
 
44
44
  /** Listen to workflow error events, excludes catalog workflow */
45
- export const onWorkflowError = handler => messageBus.on( BusEventType.WORKFLOW_ERROR, ( { id, runId, name, duration, error } ) =>
46
- WORKFLOW_CATALOG !== name ? safeInvoke( handler, { id, runId, name, duration, error }, 'onWorkflowError' ) : null );
45
+ export const onWorkflowError = handler => messageBus.on( BusEventType.WORKFLOW_ERROR, ( { id, name, duration, error } ) =>
46
+ WORKFLOW_CATALOG !== name ? safeInvoke( handler, { id, name, duration, error }, 'onWorkflowError' ) : null );
47
47
 
48
48
  /** Generic listener for events emitted elsewhere (outside core) */
49
49
  export const on = ( eventName, handler ) => messageBus.on( `external:${eventName}`, payload =>
@@ -63,18 +63,7 @@ export type WorkflowContext<
63
63
  *
64
64
  * @see {@link https://docs.temporal.io/workflow-execution/workflowid-runid#workflow-id}
65
65
  */
66
- workflowId: string,
67
-
68
- /**
69
- * Internal Temporal run id for the current execution attempt.
70
- *
71
- * A single `workflowId` can map to multiple `runId`s when a workflow is
72
- * retried, reset, or continued-as-new. The current run can be pinned in
73
- * downstream `/workflow/{id}/runs/{rid}/...` API calls.
74
- *
75
- * @see {@link https://docs.temporal.io/workflow-execution/workflowid-runid#run-id}
76
- */
77
- runId: string
66
+ workflowId: string
78
67
  }
79
68
  };
80
69
 
@@ -44,20 +44,15 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
44
44
  // this returns a plain function, for example, in unit tests
45
45
  if ( !inWorkflowContext() ) {
46
46
  validateWithSchema( inputSchema, input, `Workflow ${name} input` );
47
- const context = Context.build( {
48
- workflowId: 'test-workflow',
49
- runId: 'test-run',
50
- continueAsNew: async () => {},
51
- isContinueAsNewSuggested: () => false
52
- } );
47
+ const context = Context.build( { workflowId: 'test-workflow', continueAsNew: async () => {}, isContinueAsNewSuggested: () => false } );
53
48
  const output = await fn( input, deepMerge( context, extra.context ) );
54
49
  validateWithSchema( outputSchema, output, `Workflow ${name} output` );
55
50
  return output;
56
51
  }
57
52
 
58
- const { workflowId, runId, memo, startTime } = workflowInfo();
53
+ const { workflowId, memo, startTime } = workflowInfo();
59
54
 
60
- const context = Context.build( { workflowId, runId, continueAsNew, isContinueAsNewSuggested: () => workflowInfo().continueAsNewSuggested } );
55
+ const context = Context.build( { workflowId, continueAsNew, isContinueAsNewSuggested: () => workflowInfo().continueAsNewSuggested } );
61
56
 
62
57
  // Root workflows will not have the execution context yet, since it is set here.
63
58
  const isRoot = !memo.executionContext;
@@ -67,7 +62,6 @@ export function workflow( { name, description, inputSchema, outputSchema, fn, op
67
62
  It will be used to as context for tracing (connecting events) */
68
63
  const executionContext = memo.executionContext ?? {
69
64
  workflowId,
70
- runId,
71
65
  workflowName: name,
72
66
  disableTrace,
73
67
  startTime: startTime.getTime()
@@ -7,12 +7,11 @@ export class Context {
7
7
  * Builds a new context instance
8
8
  * @param {object} options - Arguments to build a new context instance
9
9
  * @param {string} workflowId
10
- * @param {string} runId
11
10
  * @param {function} continueAsNew
12
11
  * @param {function} isContinueAsNewSuggested
13
12
  * @returns {object} context
14
13
  */
15
- static build( { workflowId, runId, continueAsNew, isContinueAsNewSuggested } ) {
14
+ static build( { workflowId, continueAsNew, isContinueAsNewSuggested } ) {
16
15
  return {
17
16
  /**
18
17
  * Control namespace: This object adds functions to interact with Temporal flow mechanisms
@@ -25,8 +24,7 @@ export class Context {
25
24
  * Info namespace: abstracts workflowInfo()
26
25
  */
27
26
  info: {
28
- workflowId,
29
- runId
27
+ workflowId
30
28
  }
31
29
  };
32
30
  }
@@ -11,8 +11,8 @@ export const sinks = {
11
11
  workflow: {
12
12
  start: {
13
13
  fn: ( workflowInfo, input ) => {
14
- const { workflowId: id, runId, workflowType: name, memo: { parentId, executionContext } } = workflowInfo;
15
- messageBus.emit( BusEventType.WORKFLOW_START, { id, runId, name } );
14
+ const { workflowId: id, workflowType: name, memo: { parentId, executionContext } } = workflowInfo;
15
+ messageBus.emit( BusEventType.WORKFLOW_START, { id, name } );
16
16
  if ( executionContext ) { // filters out internal workflows
17
17
  Tracing.addEventStart( { id, kind: ComponentType.WORKFLOW, name, details: input, parentId, executionContext } );
18
18
  }
@@ -22,8 +22,8 @@ export const sinks = {
22
22
 
23
23
  end: {
24
24
  fn: ( workflowInfo, output ) => {
25
- const { workflowId: id, runId, workflowType: name, startTime, memo: { executionContext } } = workflowInfo;
26
- messageBus.emit( BusEventType.WORKFLOW_END, { id, runId, name, duration: Date.now() - startTime.getTime() } );
25
+ const { workflowId: id, workflowType: name, startTime, memo: { executionContext } } = workflowInfo;
26
+ messageBus.emit( BusEventType.WORKFLOW_END, { id, name, duration: Date.now() - startTime.getTime() } );
27
27
  if ( executionContext ) { // filters out internal workflows
28
28
  Tracing.addEventEnd( { id, details: output, executionContext } );
29
29
  }
@@ -33,8 +33,8 @@ export const sinks = {
33
33
 
34
34
  error: {
35
35
  fn: ( workflowInfo, error ) => {
36
- const { workflowId: id, runId, workflowType: name, startTime, memo: { executionContext } } = workflowInfo;
37
- messageBus.emit( BusEventType.WORKFLOW_ERROR, { id, runId, name, error, duration: Date.now() - startTime.getTime() } );
36
+ const { workflowId: id, workflowType: name, startTime, memo: { executionContext } } = workflowInfo;
37
+ messageBus.emit( BusEventType.WORKFLOW_ERROR, { id, name, error, duration: Date.now() - startTime.getTime() } );
38
38
  if ( executionContext ) { // filters out internal workflows
39
39
  Tracing.addEventError( { id, details: error, executionContext } );
40
40
  }
@@ -1,87 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
-
3
- const loadMock = vi.hoisted( () => vi.fn() );
4
- const emitMock = vi.hoisted( () => vi.fn() );
5
-
6
- vi.mock( '#async_storage', () => ( {
7
- Storage: { load: loadMock }
8
- } ) );
9
-
10
- vi.mock( '#bus', () => ( {
11
- messageBus: { emit: emitMock }
12
- } ) );
13
-
14
- import { emitEvent } from './events.js';
15
-
16
- describe( 'emitEvent', () => {
17
- beforeEach( () => {
18
- vi.clearAllMocks();
19
- } );
20
-
21
- it( 'forwards workflowId, runId, and activityId from executionContext', () => {
22
- loadMock.mockReturnValue( {
23
- executionContext: { workflowId: 'wf-1', runId: 'run-1' },
24
- parentId: 'act-1'
25
- } );
26
-
27
- emitEvent( 'cost:llm:request', { modelId: 'gpt-4o' } );
28
-
29
- expect( emitMock ).toHaveBeenCalledWith( 'external:cost:llm:request', {
30
- workflowId: 'wf-1',
31
- runId: 'run-1',
32
- activityId: 'act-1',
33
- modelId: 'gpt-4o'
34
- } );
35
- } );
36
-
37
- it( 'handles missing executionContext gracefully', () => {
38
- loadMock.mockReturnValue( undefined );
39
-
40
- emitEvent( 'foo:bar', { x: 1 } );
41
-
42
- expect( emitMock ).toHaveBeenCalledWith( 'external:foo:bar', {
43
- workflowId: undefined,
44
- runId: undefined,
45
- activityId: undefined,
46
- x: 1
47
- } );
48
- } );
49
-
50
- it( 'handles missing payload', () => {
51
- loadMock.mockReturnValue( {
52
- executionContext: { workflowId: 'wf-2', runId: 'run-2' },
53
- parentId: 'act-2'
54
- } );
55
-
56
- emitEvent( 'lifecycle:start' );
57
-
58
- expect( emitMock ).toHaveBeenCalledWith( 'external:lifecycle:start', {
59
- workflowId: 'wf-2',
60
- runId: 'run-2',
61
- activityId: 'act-2'
62
- } );
63
- } );
64
-
65
- it( 'does not let payload override workflowId / runId / activityId', () => {
66
- loadMock.mockReturnValue( {
67
- executionContext: { workflowId: 'wf-3', runId: 'run-3' },
68
- parentId: 'act-3'
69
- } );
70
-
71
- emitEvent( 'cost:http:request', {
72
- workflowId: 'should-be-overridden',
73
- runId: 'should-be-overridden',
74
- activityId: 'should-be-overridden',
75
- url: 'https://example.com'
76
- } );
77
-
78
- // Context fields are spread after the payload, so caller-supplied
79
- // workflowId / runId / activityId cannot escape the executionContext.
80
- expect( emitMock ).toHaveBeenCalledWith( 'external:cost:http:request', {
81
- workflowId: 'wf-3',
82
- runId: 'run-3',
83
- activityId: 'act-3',
84
- url: 'https://example.com'
85
- } );
86
- } );
87
- } );