@output.ai/cli 0.3.0-dev.pr156.05c9aa2 → 0.3.0-dev.pr156.696d4dd

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.
@@ -42,11 +42,27 @@ export type PostWorkflowRunBody = {
42
42
  /** The name of the task queue to send the workflow to */
43
43
  taskQueue?: string;
44
44
  };
45
+ /**
46
+ * File destinations for trace data
47
+ */
48
+ export type PostWorkflowRun200TraceDestinations = {
49
+ /**
50
+ * Absolute path to local trace file, or null if not saved locally
51
+ * @nullable
52
+ */
53
+ local: string | null;
54
+ /**
55
+ * Remote trace location (e.g., S3 URI), or null if not saved remotely
56
+ * @nullable
57
+ */
58
+ remote: string | null;
59
+ };
45
60
  /**
46
61
  * An object with information about the trace generated by the execution
47
62
  */
48
63
  export type PostWorkflowRun200Trace = {
49
- [key: string]: unknown;
64
+ /** File destinations for trace data */
65
+ destinations?: PostWorkflowRun200TraceDestinations;
50
66
  };
51
67
  export type PostWorkflowRun200 = {
52
68
  /** The workflow execution id */
@@ -94,11 +110,27 @@ export type GetWorkflowIdStatus200 = {
94
110
  /** An epoch timestamp representing when the workflow ended */
95
111
  completedAt?: number;
96
112
  };
113
+ /**
114
+ * File destinations for trace data
115
+ */
116
+ export type GetWorkflowIdOutput200TraceDestinations = {
117
+ /**
118
+ * Absolute path to local trace file, or null if not saved locally
119
+ * @nullable
120
+ */
121
+ local: string | null;
122
+ /**
123
+ * Remote trace location (e.g., S3 URI), or null if not saved remotely
124
+ * @nullable
125
+ */
126
+ remote: string | null;
127
+ };
97
128
  /**
98
129
  * An object with information about the trace generated by the execution
99
130
  */
100
131
  export type GetWorkflowIdOutput200Trace = {
101
- [key: string]: unknown;
132
+ /** File destinations for trace data */
133
+ destinations?: GetWorkflowIdOutput200TraceDestinations;
102
134
  };
103
135
  export type GetWorkflowIdOutput200 = {
104
136
  /** The workflow execution id */
@@ -1,7 +1,7 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { OUTPUT_FORMAT } from '#utils/constants.js';
3
3
  import { traceFormatter } from '#utils/trace_formatter.js';
4
- import { TraceReader } from '#services/trace_reader.js';
4
+ import { findTraceFile, readTraceFile } from '#services/trace_reader.js';
5
5
  export default class WorkflowDebug extends Command {
6
6
  static description = 'Get and display workflow execution trace for debugging';
7
7
  static examples = [
@@ -39,9 +39,8 @@ export default class WorkflowDebug extends Command {
39
39
  this.displayTextTrace(traceData);
40
40
  }
41
41
  async getTrace(workflowId) {
42
- const reader = new TraceReader();
43
- const tracePath = await reader.findTraceFile(workflowId);
44
- return reader.readTraceFile(tracePath);
42
+ const tracePath = await findTraceFile(workflowId);
43
+ return readTraceFile(tracePath);
45
44
  }
46
45
  outputJson(data) {
47
46
  this.log(JSON.stringify(data, null, 2));
@@ -1,15 +1,8 @@
1
- export declare class TraceReader {
2
- /**
3
- * Find trace file from workflow metadata
4
- */
5
- findTraceFile(workflowId: string): Promise<string>;
6
- /**
7
- * Read and parse trace file
8
- */
9
- readTraceFile(path: string): Promise<any>;
10
- /**
11
- * Check if a file exists
12
- */
13
- private fileExists;
14
- }
15
- export declare const traceReader: TraceReader;
1
+ /**
2
+ * Find trace file from workflow metadata
3
+ */
4
+ export declare function findTraceFile(workflowId: string): Promise<string>;
5
+ /**
6
+ * Read and parse trace file
7
+ */
8
+ export declare function readTraceFile(path: string): Promise<any>;
@@ -1,52 +1,51 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
- import { getWorkflowIdStatus } from '#api/generated/api.js';
3
- export class TraceReader {
4
- /**
5
- * Find trace file from workflow metadata
6
- */
7
- async findTraceFile(workflowId) {
8
- const status = await getWorkflowIdStatus(workflowId);
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- const tracePath = status.data?.trace?.destinations?.local;
11
- if (!tracePath) {
12
- throw new Error(`No trace file path found for workflow ${workflowId}`);
13
- }
14
- if (!await this.fileExists(tracePath)) {
15
- throw new Error(`Trace file not found at path: ${tracePath}`);
16
- }
17
- return tracePath;
2
+ import { getWorkflowIdOutput } from '#api/generated/api.js';
3
+ /**
4
+ * Check if a file exists
5
+ */
6
+ async function fileExists(path) {
7
+ try {
8
+ await stat(path);
9
+ return true;
18
10
  }
19
- /**
20
- * Read and parse trace file
21
- */
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- async readTraceFile(path) {
24
- try {
25
- const content = await readFile(path, 'utf-8');
26
- return JSON.parse(content);
27
- }
28
- catch (error) {
29
- if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
30
- throw new Error(`Trace file not found at path: ${path}`);
31
- }
32
- if (error instanceof SyntaxError) {
33
- throw new Error(`Invalid JSON in trace file: ${path}`);
34
- }
35
- throw error;
36
- }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ /**
16
+ * Find trace file from workflow metadata
17
+ */
18
+ export async function findTraceFile(workflowId) {
19
+ const response = await getWorkflowIdOutput(workflowId);
20
+ // Check if we got a successful response
21
+ if (response.status !== 200) {
22
+ throw new Error(`Failed to get workflow output for ${workflowId}`);
23
+ }
24
+ const tracePath = response.data.trace?.destinations?.local;
25
+ if (!tracePath) {
26
+ throw new Error(`No trace file path found for workflow ${workflowId}`);
27
+ }
28
+ if (!await fileExists(tracePath)) {
29
+ throw new Error(`Trace file not found at path: ${tracePath}`);
30
+ }
31
+ return tracePath;
32
+ }
33
+ /**
34
+ * Read and parse trace file
35
+ */
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ export async function readTraceFile(path) {
38
+ try {
39
+ const content = await readFile(path, 'utf-8');
40
+ return JSON.parse(content);
37
41
  }
38
- /**
39
- * Check if a file exists
40
- */
41
- async fileExists(path) {
42
- try {
43
- await stat(path);
44
- return true;
42
+ catch (error) {
43
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
44
+ throw new Error(`Trace file not found at path: ${path}`);
45
45
  }
46
- catch {
47
- return false;
46
+ if (error instanceof SyntaxError) {
47
+ throw new Error(`Invalid JSON in trace file: ${path}`);
48
48
  }
49
+ throw error;
49
50
  }
50
51
  }
51
- // Export singleton instance for convenience
52
- export const traceReader = new TraceReader();
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi, afterEach } from 'vitest';
2
- import { TraceReader } from './trace_reader.js';
2
+ import { findTraceFile, readTraceFile } from './trace_reader.js';
3
3
  // Mock file system operations
4
4
  vi.mock('node:fs/promises', () => ({
5
5
  readFile: vi.fn(),
@@ -7,7 +7,7 @@ vi.mock('node:fs/promises', () => ({
7
7
  }));
8
8
  // Mock API
9
9
  vi.mock('../api/generated/api.js', () => ({
10
- getWorkflowIdStatus: vi.fn()
10
+ getWorkflowIdOutput: vi.fn()
11
11
  }));
12
12
  describe('TraceReader', () => {
13
13
  const getMocks = async () => {
@@ -19,75 +19,110 @@ describe('TraceReader', () => {
19
19
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
20
  mockStat: fsModule.stat,
21
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- mockGetWorkflowIdStatus: apiModule.getWorkflowIdStatus
22
+ mockGetWorkflowIdOutput: apiModule.getWorkflowIdOutput
23
23
  };
24
24
  };
25
25
  afterEach(() => {
26
26
  vi.clearAllMocks();
27
27
  });
28
28
  describe('findTraceFile', () => {
29
- it('should find trace file from workflow metadata', async () => {
30
- const traceReader = new TraceReader();
31
- const { mockGetWorkflowIdStatus, mockStat } = await getMocks();
29
+ it('should find trace file from workflow output metadata', async () => {
30
+ const { mockGetWorkflowIdOutput, mockStat } = await getMocks();
32
31
  const workflowId = 'test-workflow-123';
33
32
  const expectedPath = '/app/logs/runs/test/2024-01-01_test-workflow-123.json';
34
- mockGetWorkflowIdStatus.mockResolvedValue({
33
+ mockGetWorkflowIdOutput.mockResolvedValue({
34
+ status: 200,
35
35
  data: {
36
+ workflowId,
37
+ output: { result: 'test result' },
36
38
  trace: {
37
39
  destinations: {
38
- local: expectedPath
40
+ local: expectedPath,
41
+ remote: null
39
42
  }
40
43
  }
41
44
  }
42
45
  });
43
46
  mockStat.mockResolvedValue({ isFile: () => true });
44
- const result = await traceReader.findTraceFile(workflowId);
47
+ const result = await findTraceFile(workflowId);
45
48
  expect(result).toBe(expectedPath);
46
- expect(mockGetWorkflowIdStatus).toHaveBeenCalledWith(workflowId);
49
+ expect(mockGetWorkflowIdOutput).toHaveBeenCalledWith(workflowId);
47
50
  expect(mockStat).toHaveBeenCalledWith(expectedPath);
48
51
  });
49
52
  it('should throw error when no trace path in metadata', async () => {
50
- const traceReader = new TraceReader();
51
- const { mockGetWorkflowIdStatus } = await getMocks();
53
+ const { mockGetWorkflowIdOutput } = await getMocks();
52
54
  const workflowId = 'test-workflow-456';
53
- mockGetWorkflowIdStatus.mockResolvedValue({
55
+ mockGetWorkflowIdOutput.mockResolvedValue({
56
+ status: 200,
54
57
  data: {
55
- // No trace field
58
+ workflowId,
59
+ output: { result: 'test result' },
60
+ trace: {
61
+ destinations: {
62
+ local: null,
63
+ remote: null
64
+ }
65
+ }
56
66
  }
57
67
  });
58
- await expect(traceReader.findTraceFile(workflowId))
68
+ await expect(findTraceFile(workflowId))
59
69
  .rejects.toThrow(`No trace file path found for workflow ${workflowId}`);
60
70
  });
61
71
  it('should throw error when trace file not on disk', async () => {
62
- const traceReader = new TraceReader();
63
- const { mockGetWorkflowIdStatus, mockStat } = await getMocks();
72
+ const { mockGetWorkflowIdOutput, mockStat } = await getMocks();
64
73
  const workflowId = 'test-workflow-789';
65
74
  const expectedPath = '/app/logs/runs/test/2024-01-01_test-workflow-789.json';
66
- mockGetWorkflowIdStatus.mockResolvedValue({
75
+ mockGetWorkflowIdOutput.mockResolvedValue({
76
+ status: 200,
67
77
  data: {
78
+ workflowId,
79
+ output: { result: 'test result' },
68
80
  trace: {
69
81
  destinations: {
70
- local: expectedPath
82
+ local: expectedPath,
83
+ remote: null
71
84
  }
72
85
  }
73
86
  }
74
87
  });
75
88
  mockStat.mockRejectedValue(new Error('ENOENT'));
76
- await expect(traceReader.findTraceFile(workflowId))
89
+ await expect(findTraceFile(workflowId))
77
90
  .rejects.toThrow(`Trace file not found at path: ${expectedPath}`);
78
91
  });
79
92
  it('should throw error when API call fails', async () => {
80
- const traceReader = new TraceReader();
81
- const { mockGetWorkflowIdStatus } = await getMocks();
93
+ const { mockGetWorkflowIdOutput } = await getMocks();
82
94
  const workflowId = 'non-existent';
83
- mockGetWorkflowIdStatus.mockRejectedValue(new Error('Workflow not found'));
84
- await expect(traceReader.findTraceFile(workflowId))
95
+ mockGetWorkflowIdOutput.mockRejectedValue(new Error('Workflow not found'));
96
+ await expect(findTraceFile(workflowId))
85
97
  .rejects.toThrow('Workflow not found');
86
98
  });
99
+ it('should handle missing trace property gracefully', async () => {
100
+ const { mockGetWorkflowIdOutput } = await getMocks();
101
+ const workflowId = 'test-workflow-no-trace';
102
+ mockGetWorkflowIdOutput.mockResolvedValue({
103
+ status: 200,
104
+ data: {
105
+ workflowId,
106
+ output: { result: 'test result' }
107
+ // No trace property at all
108
+ }
109
+ });
110
+ await expect(findTraceFile(workflowId))
111
+ .rejects.toThrow(`No trace file path found for workflow ${workflowId}`);
112
+ });
113
+ it('should throw error when workflow not found (404)', async () => {
114
+ const { mockGetWorkflowIdOutput } = await getMocks();
115
+ const workflowId = 'non-existent-workflow';
116
+ mockGetWorkflowIdOutput.mockResolvedValue({
117
+ status: 404,
118
+ data: void 0
119
+ });
120
+ await expect(findTraceFile(workflowId))
121
+ .rejects.toThrow(`Failed to get workflow output for ${workflowId}`);
122
+ });
87
123
  });
88
124
  describe('readTraceFile', () => {
89
125
  it('should read and parse JSON file successfully', async () => {
90
- const traceReader = new TraceReader();
91
126
  const { mockReadFile } = await getMocks();
92
127
  const path = '/logs/test.json';
93
128
  const traceData = {
@@ -95,36 +130,33 @@ describe('TraceReader', () => {
95
130
  events: []
96
131
  };
97
132
  mockReadFile.mockResolvedValue(JSON.stringify(traceData));
98
- const result = await traceReader.readTraceFile(path);
133
+ const result = await readTraceFile(path);
99
134
  expect(result).toEqual(traceData);
100
135
  expect(mockReadFile).toHaveBeenCalledWith(path, 'utf-8');
101
136
  });
102
137
  it('should throw error for non-existent file', async () => {
103
- const traceReader = new TraceReader();
104
138
  const { mockReadFile } = await getMocks();
105
139
  const path = '/logs/missing.json';
106
140
  const error = new Error('ENOENT');
107
141
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
142
  error.code = 'ENOENT';
109
143
  mockReadFile.mockRejectedValue(error);
110
- await expect(traceReader.readTraceFile(path))
144
+ await expect(readTraceFile(path))
111
145
  .rejects.toThrow(`Trace file not found at path: ${path}`);
112
146
  });
113
147
  it('should throw error for invalid JSON', async () => {
114
- const traceReader = new TraceReader();
115
148
  const { mockReadFile } = await getMocks();
116
149
  const path = '/logs/invalid.json';
117
150
  mockReadFile.mockResolvedValue('invalid json {');
118
- await expect(traceReader.readTraceFile(path))
151
+ await expect(readTraceFile(path))
119
152
  .rejects.toThrow(`Invalid JSON in trace file: ${path}`);
120
153
  });
121
154
  it('should rethrow other errors', async () => {
122
- const traceReader = new TraceReader();
123
155
  const { mockReadFile } = await getMocks();
124
156
  const path = '/logs/test.json';
125
157
  const error = new Error('Permission denied');
126
158
  mockReadFile.mockRejectedValue(error);
127
- await expect(traceReader.readTraceFile(path))
159
+ await expect(readTraceFile(path))
128
160
  .rejects.toThrow('Permission denied');
129
161
  });
130
162
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@output.ai/cli",
3
- "version": "0.3.0-dev.pr156.05c9aa2",
3
+ "version": "0.3.0-dev.pr156.696d4dd",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",