@valerypopoff/rivet2-cli 2.0.11 → 2.1.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/README.md +12 -0
- package/bin/cli.js +28 -2
- package/bin/cliRuntime.js +228 -0
- package/bin/commandInputs.js +37 -1
- package/bin/commands/doctor.js +188 -0
- package/bin/commands/list.js +81 -0
- package/bin/commands/run.js +90 -39
- package/bin/commands/serve.js +128 -202
- package/bin/commands/serveApp.js +99 -0
- package/bin/http.js +107 -0
- package/bin/output.js +32 -0
- package/dist/types/cliRuntime.d.ts +47 -0
- package/dist/types/commandInputs.d.ts +7 -1
- package/dist/types/commands/doctor.d.ts +28 -0
- package/dist/types/commands/list.d.ts +44 -0
- package/dist/types/commands/run.d.ts +32 -8
- package/dist/types/commands/serve.d.ts +28 -18
- package/dist/types/commands/serveApp.d.ts +35 -0
- package/dist/types/http.d.ts +23 -0
- package/dist/types/output.d.ts +9 -0
- package/package.json +2 -2
package/bin/commands/run.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { createProcessor
|
|
1
|
+
import { createProcessor } from '@valerypopoff/rivet2-node';
|
|
2
|
+
import { configDotenv } from 'dotenv';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
|
-
import { parseJsonInputRecord, parseKeyValueInputRecord } from '../commandInputs.js';
|
|
4
|
+
import { parseJsonInputRecord, parseJsonInputRecordFromFile, parseJsonKeyValueInputRecord, parseKeyValueInputRecord, throwIfConflictingInputSources, } from '../commandInputs.js';
|
|
5
|
+
import { addDatasetOptions, addProviderOptions, loadProjectRuntime, throwIfInvalidGraph, throwIfNoMainGraph, withCliProcessorOptions, } from '../cliRuntime.js';
|
|
6
|
+
import { shapeOutputs, validateOutputShapeOptions, writeJsonOutput } from '../output.js';
|
|
4
7
|
export function makeCommand(y) {
|
|
5
|
-
|
|
8
|
+
const command = addProviderOptions(y)
|
|
6
9
|
.positional('projectFile', {
|
|
7
10
|
describe: 'The project file to run',
|
|
8
11
|
type: 'string',
|
|
@@ -16,17 +19,49 @@ export function makeCommand(y) {
|
|
|
16
19
|
describe: 'Read inputs from stdin as JSON',
|
|
17
20
|
type: 'boolean',
|
|
18
21
|
default: false,
|
|
22
|
+
})
|
|
23
|
+
.option('inputs-file', {
|
|
24
|
+
describe: 'Read inputs from a JSON file',
|
|
25
|
+
type: 'string',
|
|
26
|
+
})
|
|
27
|
+
.option('input-json', {
|
|
28
|
+
describe: 'Adds a JSON input to the graph run using key=json',
|
|
29
|
+
type: 'string',
|
|
30
|
+
array: true,
|
|
31
|
+
default: [],
|
|
19
32
|
})
|
|
20
33
|
.option('include-cost', {
|
|
21
34
|
describe: 'Include the total cost in the output',
|
|
22
35
|
type: 'boolean',
|
|
23
36
|
default: false,
|
|
37
|
+
})
|
|
38
|
+
.option('output-key', {
|
|
39
|
+
describe: 'Print only one named output Data Value wrapper',
|
|
40
|
+
type: 'string',
|
|
41
|
+
})
|
|
42
|
+
.option('unwrap-output', {
|
|
43
|
+
describe: 'Print only the .value field from one named output',
|
|
44
|
+
type: 'string',
|
|
45
|
+
})
|
|
46
|
+
.option('output-file', {
|
|
47
|
+
describe: 'Write the JSON output to a file instead of stdout',
|
|
48
|
+
type: 'string',
|
|
24
49
|
})
|
|
25
50
|
.option('context', {
|
|
26
51
|
describe: 'Adds a context value to the graph run',
|
|
27
52
|
type: 'string',
|
|
28
53
|
array: true,
|
|
29
54
|
default: [],
|
|
55
|
+
})
|
|
56
|
+
.option('context-json', {
|
|
57
|
+
describe: 'Adds a JSON context value using key=json',
|
|
58
|
+
type: 'string',
|
|
59
|
+
array: true,
|
|
60
|
+
default: [],
|
|
61
|
+
})
|
|
62
|
+
.option('context-file', {
|
|
63
|
+
describe: 'Read context values from a JSON file',
|
|
64
|
+
type: 'string',
|
|
30
65
|
})
|
|
31
66
|
.option('input', {
|
|
32
67
|
describe: 'Adds an input to the graph run',
|
|
@@ -34,44 +69,60 @@ export function makeCommand(y) {
|
|
|
34
69
|
array: true,
|
|
35
70
|
default: [],
|
|
36
71
|
});
|
|
72
|
+
return addDatasetOptions(command);
|
|
37
73
|
}
|
|
38
74
|
export async function run(args) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
const outputs = await run();
|
|
68
|
-
if (!args.includeCost) {
|
|
69
|
-
delete outputs.cost;
|
|
70
|
-
}
|
|
71
|
-
console.log(JSON.stringify(outputs, null, 2));
|
|
75
|
+
configDotenv();
|
|
76
|
+
validateOutputShapeOptions(args);
|
|
77
|
+
const runtime = await loadProjectRuntime(args.projectFile, args);
|
|
78
|
+
throwIfNoMainGraph(runtime.project, args.graphName, runtime.projectPath, 'positional');
|
|
79
|
+
throwIfInvalidGraph(runtime.project, args.graphName, 'positional');
|
|
80
|
+
const { run: runProcessor } = createProcessor(runtime.project, withCliProcessorOptions(runtime, args, {
|
|
81
|
+
context: await readContext(args),
|
|
82
|
+
graph: args.graphName,
|
|
83
|
+
inputs: await readInputs(args),
|
|
84
|
+
}));
|
|
85
|
+
await writeJsonOutput(shapeOutputs(await runProcessor(), {
|
|
86
|
+
includeCost: args.includeCost,
|
|
87
|
+
outputKey: args.outputKey,
|
|
88
|
+
unwrapOutput: args.unwrapOutput,
|
|
89
|
+
}), args.outputFile);
|
|
90
|
+
}
|
|
91
|
+
async function readInputs(args) {
|
|
92
|
+
throwIfConflictingInputSources([
|
|
93
|
+
{ enabled: args.inputsStdin, name: '--inputs-stdin' },
|
|
94
|
+
{ enabled: args.inputsFile != null, name: '--inputs-file' },
|
|
95
|
+
{ enabled: args.input.length > 0 || args.inputJson.length > 0, name: '--input/--input-json' },
|
|
96
|
+
], 'input');
|
|
97
|
+
if (args.inputsStdin) {
|
|
98
|
+
return parseJsonInputRecord(await readStdin(), 'Input stdin');
|
|
99
|
+
}
|
|
100
|
+
if (args.inputsFile) {
|
|
101
|
+
return parseJsonInputRecordFromFile(resolve(process.cwd(), args.inputsFile), 'Input file');
|
|
72
102
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
return {
|
|
104
|
+
...parseKeyValueInputRecord(args.input, 'input'),
|
|
105
|
+
...parseJsonKeyValueInputRecord(args.inputJson, 'input-json'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function readContext(args) {
|
|
109
|
+
throwIfConflictingInputSources([
|
|
110
|
+
{ enabled: args.contextFile != null, name: '--context-file' },
|
|
111
|
+
{ enabled: args.context.length > 0 || args.contextJson.length > 0, name: '--context/--context-json' },
|
|
112
|
+
], 'context');
|
|
113
|
+
if (args.contextFile) {
|
|
114
|
+
return parseJsonInputRecordFromFile(resolve(process.cwd(), args.contextFile), 'Context file');
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
...parseKeyValueInputRecord(args.context, 'context'),
|
|
118
|
+
...parseJsonKeyValueInputRecord(args.contextJson, 'context-json'),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function readStdin() {
|
|
122
|
+
process.stdin.setEncoding('utf8');
|
|
123
|
+
let inputText = '';
|
|
124
|
+
for await (const chunk of process.stdin) {
|
|
125
|
+
inputText += chunk;
|
|
76
126
|
}
|
|
127
|
+
return inputText;
|
|
77
128
|
}
|
package/bin/commands/serve.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import { serve as serveHono } from '@hono/node-server';
|
|
2
1
|
import { createProcessor, getSingleNodeStream, loadProjectFromFile } from '@valerypopoff/rivet2-node';
|
|
3
2
|
import chalk from 'chalk';
|
|
4
|
-
import didYouMean from 'didyoumean2';
|
|
5
3
|
import { configDotenv } from 'dotenv';
|
|
6
4
|
import { Hono } from 'hono';
|
|
7
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
8
|
-
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
9
5
|
import { parseJsonInputRecord } from '../commandInputs.js';
|
|
6
|
+
import { addDatasetOptions, addProviderOptions, loadProjectRuntime, resolveServedGraphName, throwIfInvalidGraph, throwIfNoMainGraph, warnIfServerSavesDatasets, withCliProcessorOptions, withProviderProcessorOptions, } from '../cliRuntime.js';
|
|
7
|
+
import { CliHttpError, addHttpOptions, createHttpMiddleware, formatListenUrl, jsonErrorResponse, jsonTimedResponse, startHttpServer, } from '../http.js';
|
|
8
|
+
import { shapeOutputs } from '../output.js';
|
|
10
9
|
export function makeCommand(y) {
|
|
11
|
-
|
|
10
|
+
const command = addHttpOptions(addProviderOptions(y)
|
|
12
11
|
.option('port', {
|
|
13
12
|
describe: 'The port to serve on',
|
|
14
13
|
type: 'number',
|
|
15
14
|
default: 3000,
|
|
15
|
+
})
|
|
16
|
+
.option('host', {
|
|
17
|
+
describe: 'The host interface to bind to',
|
|
18
|
+
type: 'string',
|
|
19
|
+
default: '0.0.0.0',
|
|
16
20
|
})
|
|
17
21
|
.option('dev', {
|
|
18
22
|
describe: 'Run in development mode: rereads the project file on each request',
|
|
@@ -29,25 +33,21 @@ export function makeCommand(y) {
|
|
|
29
33
|
type: 'boolean',
|
|
30
34
|
default: false,
|
|
31
35
|
})
|
|
32
|
-
.option('
|
|
33
|
-
describe: '
|
|
34
|
-
type: 'string',
|
|
35
|
-
demandOption: false,
|
|
36
|
-
})
|
|
37
|
-
.option('openai-endpoint', {
|
|
38
|
-
describe: 'The OpenAI API endpoint to use for the project. If omitted, the environment variable OPENAI_ENDPOINT is used.',
|
|
36
|
+
.option('endpoint', {
|
|
37
|
+
describe: 'Expose a named endpoint alias using endpointName=graphNameOrId',
|
|
39
38
|
type: 'string',
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
type: 'string',
|
|
45
|
-
demandOption: false,
|
|
46
|
-
})
|
|
39
|
+
array: true,
|
|
40
|
+
default: [],
|
|
41
|
+
}));
|
|
42
|
+
return addDatasetOptions(command)
|
|
47
43
|
.option('expose-cost', {
|
|
48
44
|
describe: 'Expose the cost of the graph run in the response',
|
|
49
45
|
type: 'boolean',
|
|
50
46
|
default: false,
|
|
47
|
+
})
|
|
48
|
+
.option('unwrap-output', {
|
|
49
|
+
describe: 'Respond with only the .value field from one named output',
|
|
50
|
+
type: 'string',
|
|
51
51
|
})
|
|
52
52
|
.option('stream', {
|
|
53
53
|
describe: 'Turns on streaming mode. Rivet events will be sent to the client using SSE (Server-Sent Events). If this is set to a Node ID or node title, only events for that node will be sent.',
|
|
@@ -66,94 +66,96 @@ export function makeCommand(y) {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
export async function serve(args) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
configDotenv();
|
|
70
|
+
const { app, projectFilePath, servedGraphName } = await createServeApp(args);
|
|
71
|
+
startHttpServer(app, args.host, args.port);
|
|
72
|
+
console.log(chalk.green(`Serving project file ${chalk.bold.white(projectFilePath)} at ${chalk.bold.white(formatListenUrl(args.host, args.port))}.\nServing graph "${chalk.bold.white(servedGraphName)}".`));
|
|
73
|
+
}
|
|
74
|
+
export async function createServeApp(args) {
|
|
75
|
+
const runtime = await loadProjectRuntime(args.projectFile, args);
|
|
76
|
+
const endpointAliases = parseEndpointAliases(args.endpoint, runtime.project);
|
|
77
|
+
throwIfNoMainGraph(runtime.project, args.graph, runtime.projectPath);
|
|
78
|
+
throwIfInvalidGraph(runtime.project, args.graph);
|
|
79
|
+
if (args.stream != null) {
|
|
80
|
+
console.log('Streaming is enabled');
|
|
81
|
+
}
|
|
82
|
+
warnIfServerSavesDatasets(args.saveDatasets, 'CLI server');
|
|
83
|
+
if (args.streamNode != null) {
|
|
84
|
+
if (args.stream == null) {
|
|
85
|
+
throw new Error('--stream-node requires --stream.');
|
|
78
86
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
console.log(`Streaming node ${chalk.bold(args.streamNode)}`);
|
|
88
|
+
}
|
|
89
|
+
const app = new Hono();
|
|
90
|
+
app.use('*', createHttpMiddleware(args));
|
|
91
|
+
app.get('/healthz', (c) => c.json({
|
|
92
|
+
ok: true,
|
|
93
|
+
projectPath: runtime.projectPath,
|
|
94
|
+
}));
|
|
95
|
+
app.post('/', async (c) => handleGraphRequest(c, args, runtime, args.graph));
|
|
96
|
+
app.post('/endpoints/:endpointName', async (c) => {
|
|
97
|
+
const endpointName = c.req.param('endpointName');
|
|
98
|
+
const graph = endpointAliases.get(endpointName);
|
|
99
|
+
if (!graph) {
|
|
100
|
+
return jsonErrorResponse(c, new CliHttpError(`Endpoint "${endpointName}" not found.`, 404), Date.now(), 404);
|
|
84
101
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
return handleGraphRequest(c, args, runtime, graph);
|
|
103
|
+
});
|
|
104
|
+
if (args.allowSpecifyingGraphId) {
|
|
105
|
+
app.post('/:graphId', async (c) => handleGraphRequest(c, args, runtime, c.req.param('graphId')));
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
app,
|
|
109
|
+
projectFilePath: runtime.projectPath,
|
|
110
|
+
servedGraphName: resolveServedGraphName(runtime.project, args.graph),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async function handleGraphRequest(c, args, runtime, graph) {
|
|
114
|
+
const startedAt = Date.now();
|
|
115
|
+
let graphRunArgs;
|
|
116
|
+
try {
|
|
117
|
+
const project = args.dev ? await loadProjectFromFile(runtime.projectPath) : runtime.project;
|
|
118
|
+
throwIfInvalidGraph(project, graph);
|
|
119
|
+
const inputs = parseJsonInputRecord(await c.req.text(), 'Request body');
|
|
120
|
+
graphRunArgs = buildGraphRunArgs(args, runtime, project, inputs, graph);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return jsonErrorResponse(c, toBadRequestError(error), startedAt, 400);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
if (args.stream != null) {
|
|
127
|
+
const stream = await streamGraph({
|
|
128
|
+
...graphRunArgs,
|
|
129
|
+
stream: args.stream,
|
|
130
|
+
streamNode: args.streamNode,
|
|
112
131
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
console.log(chalk.green(`Serving project file ${chalk.bold.white(projectFilePath)} on port ${chalk.bold.white(args.port)}.\nServing graph "${chalk.bold.white(servedGraphName)}".`));
|
|
120
|
-
function shutdown() {
|
|
121
|
-
console.log('Shutting down...');
|
|
122
|
-
server.close((err) => {
|
|
123
|
-
if (err) {
|
|
124
|
-
console.error(err);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
process.exit(0);
|
|
132
|
+
return new Response(stream, {
|
|
133
|
+
headers: {
|
|
134
|
+
'Cache-Control': 'no-cache',
|
|
135
|
+
Connection: 'keep-alive',
|
|
136
|
+
'Content-Type': 'text/event-stream',
|
|
137
|
+
},
|
|
128
138
|
});
|
|
129
139
|
}
|
|
130
|
-
|
|
131
|
-
process.on('SIGTERM', shutdown);
|
|
140
|
+
return jsonTimedResponse(c, await runGraph(graphRunArgs), startedAt);
|
|
132
141
|
}
|
|
133
|
-
catch (
|
|
134
|
-
|
|
135
|
-
process.exit(1);
|
|
142
|
+
catch (error) {
|
|
143
|
+
return jsonErrorResponse(c, error, startedAt, 500);
|
|
136
144
|
}
|
|
137
145
|
}
|
|
138
|
-
function buildGraphRunArgs(args, project, inputs, graph) {
|
|
146
|
+
function buildGraphRunArgs(args, runtime, project, inputs, graph) {
|
|
139
147
|
return {
|
|
140
148
|
exposeCost: args.exposeCost,
|
|
141
149
|
graph,
|
|
142
150
|
inputs,
|
|
143
|
-
openaiApiKey: args.openaiApiKey,
|
|
144
|
-
openaiEndpoint: args.openaiEndpoint,
|
|
145
|
-
openaiOrganization: args.openaiOrganization,
|
|
146
151
|
project,
|
|
152
|
+
providerOptions: args,
|
|
153
|
+
runtime,
|
|
154
|
+
unwrapOutput: args.unwrapOutput,
|
|
147
155
|
};
|
|
148
156
|
}
|
|
149
|
-
async function streamGraph({ project, inputs, graph,
|
|
150
|
-
const { run, processor, getSSEStream } = createProcessor(project, buildStreamingGraphProcessorOptions({
|
|
151
|
-
graph,
|
|
152
|
-
inputs,
|
|
153
|
-
openaiApiKey,
|
|
154
|
-
openaiEndpoint,
|
|
155
|
-
openaiOrganization,
|
|
156
|
-
}));
|
|
157
|
+
async function streamGraph({ project, inputs, graph, providerOptions, runtime, stream, streamNode, }) {
|
|
158
|
+
const { run, processor, getSSEStream } = createProcessor(project, withCliProcessorOptions(runtime, providerOptions, buildStreamingGraphProcessorOptions({ graph, inputs })));
|
|
157
159
|
const responseStream = streamNode
|
|
158
160
|
? getSingleNodeStream(processor, streamNode)
|
|
159
161
|
: getSSEStream(buildStreamEventFilter(stream));
|
|
@@ -163,13 +165,10 @@ async function streamGraph({ project, inputs, graph, openaiApiKey, openaiEndpoin
|
|
|
163
165
|
return responseStream;
|
|
164
166
|
}
|
|
165
167
|
export function buildGraphProcessorOptions({ inputs, graph, openaiApiKey, openaiEndpoint, openaiOrganization, }) {
|
|
166
|
-
return {
|
|
167
|
-
inputs,
|
|
168
|
+
return withProviderProcessorOptions({ openaiApiKey, openaiEndpoint, openaiOrganization }, {
|
|
168
169
|
graph,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
openAiOrganization: openaiOrganization,
|
|
172
|
-
};
|
|
170
|
+
inputs,
|
|
171
|
+
});
|
|
173
172
|
}
|
|
174
173
|
export function buildStreamingGraphProcessorOptions(args) {
|
|
175
174
|
return {
|
|
@@ -181,123 +180,50 @@ export function buildStreamEventFilter(stream) {
|
|
|
181
180
|
const streamTarget = stream?.trim();
|
|
182
181
|
if (!streamTarget) {
|
|
183
182
|
return {
|
|
184
|
-
nodeStart: true,
|
|
185
183
|
nodeFinish: true,
|
|
184
|
+
nodeStart: true,
|
|
186
185
|
partialOutputs: true,
|
|
187
186
|
};
|
|
188
187
|
}
|
|
189
188
|
return {
|
|
190
|
-
nodeStart: [streamTarget],
|
|
191
189
|
nodeFinish: [streamTarget],
|
|
190
|
+
nodeStart: [streamTarget],
|
|
192
191
|
partialOutputs: [streamTarget],
|
|
193
192
|
};
|
|
194
193
|
}
|
|
195
|
-
async function runGraph({ project, inputs, graph,
|
|
196
|
-
const { run } = createProcessor(project,
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
openaiOrganization,
|
|
202
|
-
}));
|
|
203
|
-
const outputs = await run();
|
|
204
|
-
if (!exposeCost) {
|
|
205
|
-
delete outputs.cost;
|
|
206
|
-
}
|
|
207
|
-
return outputs;
|
|
208
|
-
}
|
|
209
|
-
function getGraphSummaries(project) {
|
|
210
|
-
return Object.values(project.graphs).map((graph) => ({
|
|
211
|
-
id: graph.metadata.id,
|
|
212
|
-
name: graph.metadata.name,
|
|
213
|
-
}));
|
|
214
|
-
}
|
|
215
|
-
function findGraph(project, graphIdOrName) {
|
|
216
|
-
if (!graphIdOrName) {
|
|
217
|
-
return undefined;
|
|
218
|
-
}
|
|
219
|
-
return getGraphSummaries(project).find((graph) => graph.id === graphIdOrName || graph.name === graphIdOrName);
|
|
220
|
-
}
|
|
221
|
-
function formatGraphList(project) {
|
|
222
|
-
return getGraphSummaries(project)
|
|
223
|
-
.map((graph) => `- "${graph.name}" (${graph.id})`)
|
|
224
|
-
.join('\n');
|
|
225
|
-
}
|
|
226
|
-
function resolveServedGraphName(project, graph) {
|
|
227
|
-
const servedGraph = findGraph(project, graph ?? project.metadata.mainGraphId);
|
|
228
|
-
if (!servedGraph) {
|
|
229
|
-
throw new Error(`Project main graph "${project.metadata.mainGraphId}" was not found in the project file.`);
|
|
230
|
-
}
|
|
231
|
-
return servedGraph.name;
|
|
232
|
-
}
|
|
233
|
-
function throwIfNoMainGraph(project, graph, projectFilePath) {
|
|
234
|
-
if (project.metadata.mainGraphId || graph) {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const validGraphs = getGraphSummaries(project);
|
|
238
|
-
if (validGraphs.length === 0) {
|
|
239
|
-
throw new Error('No graphs found in the project file. Please edit the project file in Rivet and add a graph.');
|
|
240
|
-
}
|
|
241
|
-
const firstExample = `rivet serve ${projectFilePath} --graph ${validGraphs[0].id}`;
|
|
242
|
-
const secondExample = `rivet serve ${projectFilePath} --graph "${validGraphs[0].name}"`;
|
|
243
|
-
throw new Error(`No graph name provided, and project does not specify a main graph. Valid graphs are: \n\n${formatGraphList(project)}\n\nUse either the graph's name or its ID. For example, \n- \`${chalk.bold(firstExample)}\` or\n- \`${chalk.bold(secondExample)}\``);
|
|
244
|
-
}
|
|
245
|
-
function throwIfInvalidGraph(project, graph) {
|
|
246
|
-
if (!graph) {
|
|
247
|
-
const mainGraphId = project.metadata.mainGraphId;
|
|
248
|
-
if (!mainGraphId || findGraph(project, mainGraphId)) {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
throw new Error(`Project main graph "${mainGraphId}" was not found in the project file.`);
|
|
252
|
-
}
|
|
253
|
-
if (findGraph(project, graph)) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
const validGraphsAndIds = getGraphSummaries(project).flatMap((graph) => [graph.id, graph.name]);
|
|
257
|
-
const suggestion = didYouMean(graph, validGraphsAndIds);
|
|
258
|
-
if (suggestion) {
|
|
259
|
-
throw new Error(`Graph "${graph}" not found in project file. Did you mean \`${chalk.bold(`--graph "${suggestion}"`)}\`?`);
|
|
260
|
-
}
|
|
261
|
-
throw new Error(`Graph "${graph}" not found in project file. Valid graphs are: \n${formatGraphList(project)}`);
|
|
262
|
-
}
|
|
263
|
-
async function getProjectFile(initialProjectFilePath) {
|
|
264
|
-
let projectFilePath = resolve(process.cwd(), initialProjectFilePath ?? (await getProjectFilePathFromDirectory(process.cwd())));
|
|
265
|
-
await throwIfMissingFile(projectFilePath);
|
|
266
|
-
if ((await stat(projectFilePath)).isDirectory()) {
|
|
267
|
-
projectFilePath = await getProjectFilePathFromDirectory(projectFilePath);
|
|
268
|
-
}
|
|
269
|
-
return projectFilePath;
|
|
270
|
-
}
|
|
271
|
-
async function getProjectFilePathFromDirectory(directory) {
|
|
272
|
-
const files = await readdir(directory);
|
|
273
|
-
const projectFiles = files.filter((file) => extname(file).toLowerCase() === '.rivet-project');
|
|
274
|
-
if (projectFiles.length === 0) {
|
|
275
|
-
throw new Error('No project file found in the current directory. Project files should end with .rivet-project.');
|
|
276
|
-
}
|
|
277
|
-
if (projectFiles.length > 1) {
|
|
278
|
-
throw new Error(`Multiple project files found in the current directory. Please specify which one to serve: \n${projectFiles.join('\n')}`);
|
|
279
|
-
}
|
|
280
|
-
return join(directory, projectFiles[0]);
|
|
194
|
+
async function runGraph({ project, inputs, graph, providerOptions, exposeCost, runtime, unwrapOutput, }) {
|
|
195
|
+
const { run } = createProcessor(project, withCliProcessorOptions(runtime, providerOptions, { graph, inputs }));
|
|
196
|
+
return shapeOutputs(await run(), {
|
|
197
|
+
includeCost: exposeCost,
|
|
198
|
+
unwrapOutput,
|
|
199
|
+
});
|
|
281
200
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
throw err;
|
|
201
|
+
export function parseEndpointAliases(endpointSpecs, project) {
|
|
202
|
+
const aliases = new Map();
|
|
203
|
+
for (const spec of endpointSpecs) {
|
|
204
|
+
const separatorIndex = spec.indexOf('=');
|
|
205
|
+
if (separatorIndex <= 0) {
|
|
206
|
+
throw new Error(`Invalid endpoint "${spec}". Expected endpointName=graphNameOrId.`);
|
|
289
207
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
208
|
+
const endpointName = spec.slice(0, separatorIndex).trim();
|
|
209
|
+
const graph = spec.slice(separatorIndex + 1).trim();
|
|
210
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(endpointName)) {
|
|
211
|
+
throw new Error(`Invalid endpoint name "${endpointName}". Endpoint names must use letters, numbers, hyphens, or underscores, and must start with a letter or number.`);
|
|
293
212
|
}
|
|
294
|
-
|
|
295
|
-
throw new Error(`
|
|
213
|
+
if (!graph) {
|
|
214
|
+
throw new Error(`Invalid endpoint "${spec}". Expected a graph name or ID after "=".`);
|
|
296
215
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
throw new Error(`Could not find project file "${filePath}". Did you mean "${join(dirname(filePath), suggestion)}"?`);
|
|
216
|
+
if (aliases.has(endpointName)) {
|
|
217
|
+
throw new Error(`Duplicate endpoint "${endpointName}".`);
|
|
300
218
|
}
|
|
301
|
-
|
|
219
|
+
throwIfInvalidGraph(project, graph);
|
|
220
|
+
aliases.set(endpointName, graph);
|
|
221
|
+
}
|
|
222
|
+
return aliases;
|
|
223
|
+
}
|
|
224
|
+
function toBadRequestError(error) {
|
|
225
|
+
if (error instanceof CliHttpError) {
|
|
226
|
+
return error;
|
|
302
227
|
}
|
|
228
|
+
return new CliHttpError(error instanceof Error ? error.message : String(error), 400);
|
|
303
229
|
}
|