679 lines
18 KiB
TypeScript
679 lines
18 KiB
TypeScript
import type {
|
|
AnyRetrieveRunResult,
|
|
AnyRunShape,
|
|
ApiRequestOptions,
|
|
CreateBulkCancelActionOptions,
|
|
CreateBulkReplayActionOptions,
|
|
InferRunTypes,
|
|
ListBulkActionsQueryParams,
|
|
ListProjectRunsQueryParams,
|
|
ListRunsQueryParams,
|
|
RescheduleRunRequestBody,
|
|
RetrieveRunResult,
|
|
RunShape,
|
|
RealtimeRun,
|
|
AnyRealtimeRun,
|
|
RunSubscription,
|
|
TaskRunShape,
|
|
AnyBatchedRunHandle,
|
|
AsyncIterableStream,
|
|
ApiPromise,
|
|
RealtimeRunSkipColumns,
|
|
AbortBulkActionResponseBody,
|
|
BulkActionObject,
|
|
CanceledRunResponse,
|
|
CreateBulkActionResponseBody,
|
|
CursorPagePromise,
|
|
ListRunResponseItem,
|
|
ReplayRunResponse,
|
|
RetrieveRunResponse,
|
|
} from "@trigger.dev/core/v3";
|
|
import {
|
|
accessoryAttributes,
|
|
apiClientManager,
|
|
flattenAttributes,
|
|
isRequestOptions,
|
|
mergeRequestOptions,
|
|
} from "@trigger.dev/core/v3";
|
|
import { resolvePresignedPacketUrl } from "@trigger.dev/core/v3/utils/ioSerialization";
|
|
import type { AnyRunHandle, AnyTask } from "./shared.js";
|
|
import { tracer } from "./tracer.js";
|
|
|
|
export type {
|
|
AnyRetrieveRunResult,
|
|
AnyRunShape,
|
|
RetrieveRunResult,
|
|
RunShape,
|
|
TaskRunShape,
|
|
RealtimeRun,
|
|
AnyRealtimeRun,
|
|
};
|
|
|
|
export const runs = {
|
|
replay: replayRun,
|
|
cancel: cancelRun,
|
|
retrieve: retrieveRun,
|
|
list: listRuns,
|
|
reschedule: rescheduleRun,
|
|
bulk: {
|
|
cancel: bulkCancelRuns,
|
|
replay: bulkReplayRuns,
|
|
retrieve: retrieveBulkAction,
|
|
abort: abortBulkAction,
|
|
list: listBulkActions,
|
|
poll: pollBulkAction,
|
|
},
|
|
poll,
|
|
subscribeToRun,
|
|
subscribeToRunsWithTag,
|
|
subscribeToBatch: subscribeToRunsInBatch,
|
|
fetchStream,
|
|
};
|
|
|
|
export type ListRunsItem = ListRunResponseItem;
|
|
export type BulkAction = BulkActionObject;
|
|
|
|
function listRuns(
|
|
projectRef: string,
|
|
params?: ListProjectRunsQueryParams,
|
|
requestOptions?: ApiRequestOptions
|
|
): CursorPagePromise<typeof ListRunResponseItem>;
|
|
function listRuns(
|
|
params?: ListRunsQueryParams,
|
|
requestOptions?: ApiRequestOptions
|
|
): CursorPagePromise<typeof ListRunResponseItem>;
|
|
function listRuns(
|
|
paramsOrProjectRef?: ListRunsQueryParams | string,
|
|
paramsOrOptions?: ListRunsQueryParams | ListProjectRunsQueryParams | ApiRequestOptions,
|
|
requestOptions?: ApiRequestOptions
|
|
): CursorPagePromise<typeof ListRunResponseItem> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = listRunsRequestOptions(
|
|
paramsOrProjectRef,
|
|
paramsOrOptions,
|
|
requestOptions
|
|
);
|
|
|
|
if (typeof paramsOrProjectRef === "string") {
|
|
if (isRequestOptions(paramsOrOptions)) {
|
|
return apiClient.listProjectRuns(paramsOrProjectRef, {}, $requestOptions);
|
|
} else {
|
|
return apiClient.listProjectRuns(paramsOrProjectRef, paramsOrOptions, $requestOptions);
|
|
}
|
|
}
|
|
|
|
return apiClient.listRuns(paramsOrProjectRef, $requestOptions);
|
|
}
|
|
|
|
function listRunsRequestOptions(
|
|
paramsOrProjectRef?: ListRunsQueryParams | string,
|
|
paramsOrOptions?: ListRunsQueryParams | ListProjectRunsQueryParams | ApiRequestOptions,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiRequestOptions {
|
|
if (typeof paramsOrProjectRef === "string") {
|
|
if (isRequestOptions(paramsOrOptions)) {
|
|
return mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.list()",
|
|
icon: "runs",
|
|
attributes: {
|
|
projectRef: paramsOrProjectRef,
|
|
...accessoryAttributes({
|
|
items: [
|
|
{
|
|
text: paramsOrProjectRef,
|
|
variant: "normal",
|
|
},
|
|
],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
paramsOrOptions
|
|
);
|
|
} else {
|
|
return mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.list()",
|
|
icon: "runs",
|
|
attributes: {
|
|
projectRef: paramsOrProjectRef,
|
|
...flattenAttributes(paramsOrOptions as Record<string, unknown>, "queryParams"),
|
|
...accessoryAttributes({
|
|
items: [
|
|
{
|
|
text: paramsOrProjectRef,
|
|
variant: "normal",
|
|
},
|
|
],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
}
|
|
}
|
|
|
|
return mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.list()",
|
|
icon: "runs",
|
|
attributes: {
|
|
...flattenAttributes(paramsOrProjectRef as Record<string, unknown>, "queryParams"),
|
|
},
|
|
},
|
|
isRequestOptions(paramsOrOptions) ? paramsOrOptions : requestOptions
|
|
);
|
|
}
|
|
|
|
// Extract out the expected type of the id, can be either a string or a RunHandle
|
|
type RunId<TRunId> = TRunId extends AnyRunHandle | AnyBatchedRunHandle
|
|
? TRunId
|
|
: TRunId extends AnyTask
|
|
? string
|
|
: TRunId extends string
|
|
? TRunId
|
|
: never;
|
|
|
|
function retrieveRun<TRunId extends AnyRunHandle | AnyBatchedRunHandle | AnyTask | string>(
|
|
runId: RunId<TRunId>,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<RetrieveRunResult<TRunId>> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.retrieve()",
|
|
icon: "runs",
|
|
attributes: {
|
|
runId: typeof runId === "string" ? runId : runId.id,
|
|
...accessoryAttributes({
|
|
items: [
|
|
{
|
|
text: typeof runId === "string" ? runId : runId.id,
|
|
variant: "normal",
|
|
},
|
|
],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
prepareData: resolvePayloadAndOutputUrls,
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
const $runId = typeof runId === "string" ? runId : runId.id;
|
|
|
|
return apiClient.retrieveRun($runId, $requestOptions) as ApiPromise<RetrieveRunResult<TRunId>>;
|
|
}
|
|
|
|
async function resolvePayloadAndOutputUrls(run: AnyRetrieveRunResult) {
|
|
const resolvedRun = { ...run };
|
|
|
|
if (run.payloadPresignedUrl && run.outputPresignedUrl) {
|
|
const [payload, output] = await Promise.all([
|
|
resolvePresignedPacketUrl(run.payloadPresignedUrl, tracer),
|
|
resolvePresignedPacketUrl(run.outputPresignedUrl, tracer),
|
|
]);
|
|
|
|
resolvedRun.payload = payload;
|
|
resolvedRun.output = output;
|
|
} else if (run.payloadPresignedUrl) {
|
|
resolvedRun.payload = await resolvePresignedPacketUrl(run.payloadPresignedUrl, tracer);
|
|
} else if (run.outputPresignedUrl) {
|
|
resolvedRun.output = await resolvePresignedPacketUrl(run.outputPresignedUrl, tracer);
|
|
}
|
|
|
|
return resolvedRun;
|
|
}
|
|
|
|
function replayRun(
|
|
runId: string,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<ReplayRunResponse> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.replay()",
|
|
icon: "runs",
|
|
attributes: {
|
|
runId,
|
|
...accessoryAttributes({
|
|
items: [
|
|
{
|
|
text: runId,
|
|
variant: "normal",
|
|
},
|
|
],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.replayRun(runId, $requestOptions);
|
|
}
|
|
|
|
function cancelRun(
|
|
runId: string,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<CanceledRunResponse> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.cancel()",
|
|
icon: "runs",
|
|
attributes: {
|
|
runId,
|
|
...accessoryAttributes({
|
|
items: [
|
|
{
|
|
text: runId,
|
|
variant: "normal",
|
|
},
|
|
],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.cancelRun(runId, $requestOptions);
|
|
}
|
|
|
|
function bulkCancelRuns(
|
|
options: CreateBulkCancelActionOptions,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<CreateBulkActionResponseBody> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.bulk.cancel()",
|
|
icon: "runs",
|
|
attributes: {
|
|
...flattenAttributes(options as Record<string, unknown>, "bulkAction"),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.createBulkAction({ ...options, action: "cancel" }, $requestOptions);
|
|
}
|
|
|
|
function bulkReplayRuns(
|
|
options: CreateBulkReplayActionOptions,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<CreateBulkActionResponseBody> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.bulk.replay()",
|
|
icon: "runs",
|
|
attributes: {
|
|
...flattenAttributes(options as Record<string, unknown>, "bulkAction"),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.createBulkAction({ ...options, action: "replay" }, $requestOptions);
|
|
}
|
|
|
|
function retrieveBulkAction(
|
|
bulkActionId: string,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<BulkActionObject> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.bulk.retrieve()",
|
|
icon: "runs",
|
|
attributes: {
|
|
bulkActionId,
|
|
...accessoryAttributes({
|
|
items: [{ text: bulkActionId, variant: "normal" }],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.retrieveBulkAction(bulkActionId, $requestOptions);
|
|
}
|
|
|
|
function abortBulkAction(
|
|
bulkActionId: string,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<AbortBulkActionResponseBody> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.bulk.abort()",
|
|
icon: "runs",
|
|
attributes: {
|
|
bulkActionId,
|
|
...accessoryAttributes({
|
|
items: [{ text: bulkActionId, variant: "normal" }],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.abortBulkAction(bulkActionId, $requestOptions);
|
|
}
|
|
|
|
function listBulkActions(
|
|
params?: ListBulkActionsQueryParams,
|
|
requestOptions?: ApiRequestOptions
|
|
): CursorPagePromise<typeof BulkActionObject> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.bulk.list()",
|
|
icon: "runs",
|
|
attributes: {
|
|
...flattenAttributes(params as Record<string, unknown>, "queryParams"),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.listBulkActions(params, $requestOptions);
|
|
}
|
|
|
|
async function pollBulkAction(
|
|
bulkActionId: string,
|
|
options?: { pollIntervalMs?: number },
|
|
requestOptions?: ApiRequestOptions
|
|
): Promise<BulkActionObject> {
|
|
let attempts = 0;
|
|
|
|
while (attempts++ < MAX_POLL_ATTEMPTS) {
|
|
const bulkAction = await retrieveBulkAction(bulkActionId, requestOptions);
|
|
|
|
if (bulkAction.status !== "PENDING") {
|
|
return bulkAction;
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, options?.pollIntervalMs ?? 1000));
|
|
}
|
|
|
|
throw new Error(`Bulk action ${bulkActionId} did not finish after ${MAX_POLL_ATTEMPTS} attempts`);
|
|
}
|
|
|
|
function rescheduleRun(
|
|
runId: string,
|
|
body: RescheduleRunRequestBody,
|
|
requestOptions?: ApiRequestOptions
|
|
): ApiPromise<RetrieveRunResponse> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
const $requestOptions = mergeRequestOptions(
|
|
{
|
|
tracer,
|
|
name: "runs.reschedule()",
|
|
icon: "runs",
|
|
attributes: {
|
|
runId,
|
|
...accessoryAttributes({
|
|
items: [
|
|
{
|
|
text: runId,
|
|
variant: "normal",
|
|
},
|
|
],
|
|
style: "codepath",
|
|
}),
|
|
},
|
|
},
|
|
requestOptions
|
|
);
|
|
|
|
return apiClient.rescheduleRun(runId, body, $requestOptions);
|
|
}
|
|
|
|
export type PollOptions = { pollIntervalMs?: number };
|
|
|
|
const MAX_POLL_ATTEMPTS = 500;
|
|
|
|
async function poll<TRunId extends AnyRunHandle | AnyTask | string>(
|
|
runId: RunId<TRunId>,
|
|
options?: { pollIntervalMs?: number },
|
|
requestOptions?: ApiRequestOptions
|
|
) {
|
|
let attempts = 0;
|
|
|
|
while (attempts++ < MAX_POLL_ATTEMPTS) {
|
|
const run = await runs.retrieve(runId, requestOptions);
|
|
|
|
if (run.isCompleted) {
|
|
return run;
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, options?.pollIntervalMs ?? 1000));
|
|
}
|
|
|
|
throw new Error(
|
|
`Run ${
|
|
typeof runId === "string" ? runId : runId.id
|
|
} did not complete after ${MAX_POLL_ATTEMPTS} attempts`
|
|
);
|
|
}
|
|
|
|
export type SubscribeToRunOptions = {
|
|
/**
|
|
* Whether to close the subscription when the run completes
|
|
*
|
|
* @default true
|
|
*
|
|
* Set this to false if you are making updates to the run metadata after completion through child runs
|
|
*/
|
|
stopOnCompletion?: boolean;
|
|
|
|
/**
|
|
* Skip columns from the subscription.
|
|
*
|
|
* @default []
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* runs.subscribeToRun("123", { skipColumns: ["payload", "output"] });
|
|
* ```
|
|
*/
|
|
skipColumns?: RealtimeRunSkipColumns;
|
|
|
|
/**
|
|
* An AbortSignal to cancel the subscription.
|
|
*
|
|
* When the signal is aborted, the underlying SSE connection is closed
|
|
* and the async iterator completes.
|
|
*/
|
|
signal?: AbortSignal;
|
|
};
|
|
|
|
/**
|
|
* Subscribes to real-time updates for a specific run.
|
|
*
|
|
* This function allows you to receive real-time updates whenever a run changes, including:
|
|
* - Status changes in the run lifecycle
|
|
* - Tag additions or removals
|
|
* - Metadata updates
|
|
*
|
|
* @template TRunId - The type parameter extending AnyRunHandle, AnyTask, or string
|
|
* @param {RunId<TRunId>} runId - The ID of the run to subscribe to. Can be a string ID, RunHandle, or Task
|
|
* @param {SubscribeToRunOptions} [options] - Optional configuration for the subscription
|
|
* @param {boolean} [options.stopOnCompletion=true] - Whether to close the subscription when the run completes
|
|
* @returns {RunSubscription<InferRunTypes<TRunId>>} An async iterator that yields updated run objects
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // Subscribe using a run handle
|
|
* const handle = await tasks.trigger("my-task", { some: "data" });
|
|
* for await (const run of runs.subscribeToRun(handle.id)) {
|
|
* console.log("Run updated:", run);
|
|
* }
|
|
*
|
|
* // Subscribe with type safety
|
|
* for await (const run of runs.subscribeToRun<typeof myTask>(runId)) {
|
|
* console.log("Payload:", run.payload.some);
|
|
* if (run.output) {
|
|
* console.log("Output:", run.output);
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
function subscribeToRun<TRunId extends AnyRunHandle | AnyTask | string>(
|
|
runId: RunId<TRunId>,
|
|
options?: SubscribeToRunOptions
|
|
): RunSubscription<InferRunTypes<TRunId>> {
|
|
const $runId = typeof runId === "string" ? runId : runId.id;
|
|
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
return apiClient.subscribeToRun($runId, {
|
|
closeOnComplete:
|
|
typeof options?.stopOnCompletion === "boolean" ? options.stopOnCompletion : true,
|
|
skipColumns: options?.skipColumns,
|
|
signal: options?.signal,
|
|
});
|
|
}
|
|
|
|
export type SubscribeToRunsFilterOptions = {
|
|
/**
|
|
* Filter runs by the time they were created. You must specify the duration string like "1h", "10s", "30m", etc.
|
|
*
|
|
* @example
|
|
* "1h" - 1 hour ago
|
|
* "10s" - 10 seconds ago
|
|
* "30m" - 30 minutes ago
|
|
* "1d" - 1 day ago
|
|
* "1w" - 1 week ago
|
|
*
|
|
* The maximum duration is 1 week
|
|
*
|
|
* @note The timestamp will be calculated on the server side when you first subscribe to the runs.
|
|
*
|
|
*/
|
|
createdAt?: string;
|
|
|
|
/**
|
|
* Skip columns from the subscription.
|
|
*
|
|
* @default []
|
|
*/
|
|
skipColumns?: RealtimeRunSkipColumns;
|
|
};
|
|
|
|
/**
|
|
* Subscribes to real-time updates for all runs that have specific tags.
|
|
*
|
|
* This function allows you to monitor multiple runs simultaneously by filtering on tags.
|
|
* You'll receive updates whenever any run with the specified tag(s) changes.
|
|
*
|
|
* @template TTasks - The type parameter extending AnyTask for type-safe payload and output
|
|
* @param {string | string[]} tag - A single tag or array of tags to filter runs
|
|
* @returns {RunSubscription<InferRunTypes<TTasks>>} An async iterator that yields updated run objects
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // Subscribe to runs with a single tag
|
|
* for await (const run of runs.subscribeToRunsWithTag("user:1234")) {
|
|
* console.log("Run updated:", run);
|
|
* }
|
|
*
|
|
* // Subscribe with multiple tags and type safety
|
|
* for await (const run of runs.subscribeToRunsWithTag<typeof myTask | typeof otherTask>(["tag1", "tag2"])) {
|
|
* switch (run.taskIdentifier) {
|
|
* case "my-task":
|
|
* console.log("MyTask output:", run.output.foo);
|
|
* break;
|
|
* case "other-task":
|
|
* console.log("OtherTask output:", run.output.bar);
|
|
* break;
|
|
* }
|
|
* }
|
|
* ```
|
|
*/
|
|
function subscribeToRunsWithTag<TTasks extends AnyTask>(
|
|
tag: string | string[],
|
|
filters?: SubscribeToRunsFilterOptions,
|
|
options?: { signal?: AbortSignal }
|
|
): RunSubscription<InferRunTypes<TTasks>> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
return apiClient.subscribeToRunsWithTag<InferRunTypes<TTasks>>(tag, filters, {
|
|
...(options ? { signal: options.signal } : {}),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Subscribes to real-time updates for all runs within a specific batch.
|
|
*
|
|
* Use this function when you've triggered multiple runs using `batchTrigger` and want
|
|
* to monitor all runs in that batch. You'll receive updates whenever any run in the batch changes.
|
|
*
|
|
* @template TTasks - The type parameter extending AnyTask for type-safe payload and output
|
|
* @param {string} batchId - The ID of the batch to subscribe to
|
|
* @returns {RunSubscription<InferRunTypes<TTasks>>} An async iterator that yields updated run objects
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // Subscribe to all runs in a batch
|
|
* for await (const run of runs.subscribeToRunsInBatch("batch-123")) {
|
|
* console.log("Batch run updated:", run);
|
|
* }
|
|
*
|
|
* // Subscribe with type safety
|
|
* for await (const run of runs.subscribeToRunsInBatch<typeof myTask>("batch-123")) {
|
|
* console.log("Run payload:", run.payload);
|
|
* if (run.output) {
|
|
* console.log("Run output:", run.output);
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @note The run objects received will include standard fields like id, status, payload, output,
|
|
* createdAt, updatedAt, tags, and more. See the Run object documentation for full details.
|
|
*/
|
|
function subscribeToRunsInBatch<TTasks extends AnyTask>(
|
|
batchId: string
|
|
): RunSubscription<InferRunTypes<TTasks>> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
return apiClient.subscribeToBatch<InferRunTypes<TTasks>>(batchId);
|
|
}
|
|
|
|
/**
|
|
* Fetches a stream of data from a run's stream key.
|
|
*/
|
|
async function fetchStream<T>(runId: string, streamKey: string): Promise<AsyncIterableStream<T>> {
|
|
const apiClient = apiClientManager.clientOrThrow();
|
|
|
|
return await apiClient.fetchStream(runId, streamKey);
|
|
}
|