refactor(ts): make port effect native

This commit is contained in:
elpresidank 2026-06-06 10:33:10 -05:00
parent 2868ced2d3
commit b6759e75df
113 changed files with 4140 additions and 4554 deletions

View file

@ -18,9 +18,8 @@ import {
type LlmProvider,
type ProcessorConfig,
type LlmResult,
type LlmChunk,
} from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
import { Effect, Stream } from "effect";
import {
llmStreamPart,
makeTextCompletionLayer,
@ -28,7 +27,6 @@ import {
providerStatusError,
requiredString,
streamTextCompletionChunks,
toAsyncGenerator,
type TextCompletionConfigError,
type TextCompletionRuntimeError,
} from "./common.ts";
@ -89,7 +87,7 @@ const mapAzureOpenAIError = (error: unknown): TextCompletionRuntimeError =>
const makeAzureOpenAIProviderFromClient = (
resolved: ResolvedAzureOpenAIConfig,
client: AzureOpenAI,
): LlmProvider => {
): LlmProvider<TextCompletionRuntimeError> => {
const {
defaultModel,
defaultTemperature,
@ -102,31 +100,29 @@ const makeAzureOpenAIProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): Promise<LlmResult> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
return Effect.runPromise(
Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: modelName,
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
max_completion_tokens: maxOutput,
}),
catch: mapAzureOpenAIError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.choices[0].message.content ?? "",
inToken: resp.usage?.prompt_tokens ?? 0,
outToken: resp.usage?.completion_tokens ?? 0,
return Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: modelName,
})),
),
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
max_completion_tokens: maxOutput,
}),
catch: mapAzureOpenAIError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.choices[0].message.content ?? "",
inToken: resp.usage?.prompt_tokens ?? 0,
outToken: resp.usage?.completion_tokens ?? 0,
model: modelName,
})),
);
},
supportsStreaming: () => true,
@ -135,11 +131,11 @@ const makeAzureOpenAIProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): AsyncGenerator<LlmChunk> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
const stream = Stream.fromEffect(
return Stream.fromEffect(
Effect.tryPromise({
try: () =>
client.chat.completions.create({
@ -169,13 +165,13 @@ const makeAzureOpenAIProviderFromClient = (
})
),
);
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapAzureOpenAIError);
},
} satisfies LlmProvider;
} satisfies LlmProvider<TextCompletionRuntimeError>;
};
export function makeAzureOpenAIProvider(config: AzureOpenAIProcessorConfig): LlmProvider {
export function makeAzureOpenAIProvider(
config: AzureOpenAIProcessorConfig,
): LlmProvider<TextCompletionRuntimeError> {
return Effect.runSync(makeAzureOpenAIProviderEffect(config));
}
@ -217,12 +213,6 @@ export const program = makeFlowProcessorProgram<
layer: (config) => makeTextCompletionLayer(makeAzureOpenAIProviderEffect(config)),
});
const azureOpenAITextCompletionRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return azureOpenAITextCompletionRuntime.runPromise(program);
}
export function runMain(): void {
NodeRuntime.runMain(program);
}

View file

@ -14,7 +14,7 @@ import {
type LlmProvider,
type ProcessorConfig,
} from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Redacted } from "effect";
import { Effect, Layer, Redacted } from "effect";
import { FetchHttpClient } from "effect/unstable/http";
import {
makeLanguageModelProvider,
@ -55,30 +55,31 @@ const loadClaudeConfig = Effect.fn("loadClaudeConfig")(function* (config: Claude
} satisfies ResolvedClaudeConfig;
});
const makeClaudeRuntime = (apiKey: string) =>
ManagedRuntime.make(
AnthropicClient.layer({
apiKey: Redacted.make(apiKey),
}).pipe(
Layer.provide(FetchHttpClient.layer),
),
const makeClaudeLayer = (apiKey: string) =>
AnthropicClient.layer({
apiKey: Redacted.make(apiKey),
}).pipe(
Layer.provide(FetchHttpClient.layer),
);
export function makeClaudeProvider(config: ClaudeProcessorConfig): LlmProvider {
return Effect.runSync(makeClaudeProviderEffect(config));
export function makeClaudeProvider(
config: ClaudeProcessorConfig,
): LlmProvider<TextCompletionRuntimeError> {
return Effect.runSync(Effect.scoped(makeClaudeProviderEffect(config)));
}
export const makeClaudeProviderEffect = Effect.fn("makeClaudeProvider")(function* (
config: ClaudeProcessorConfig,
) {
const resolved = yield* loadClaudeConfig(config);
const context = yield* Layer.build(makeClaudeLayer(resolved.apiKey));
yield* Effect.log("[Claude] LLM service initialized");
return makeLanguageModelProvider({
provider: "Claude",
defaultModel: resolved.defaultModel,
defaultTemperature: resolved.defaultTemperature,
runtime: makeClaudeRuntime(resolved.apiKey),
context,
makeLanguageModel: ({ model, temperature }) =>
AnthropicLanguageModel.make({
model,
@ -110,12 +111,6 @@ export const program = makeFlowProcessorProgram<
layer: (config) => makeTextCompletionLayer(makeClaudeProviderEffect(config)),
});
const claudeTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return claudeTextCompletionRuntime.runPromise(program);
}
export function runMain(): void {
NodeRuntime.runMain(program);
}

View file

@ -7,10 +7,11 @@ import {
type LlmResult,
type LlmProvider,
} from "@trustgraph/base";
import { Config, Effect, Layer, ManagedRuntime, Match, Ref, Result, Stream } from "effect";
import { Config, Context, Effect, Layer, Match, Ref, Result, Stream } from "effect";
import * as O from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as S from "effect/Schema";
import type * as Scope from "effect/Scope";
import { AiError, LanguageModel, Prompt, Response } from "effect/unstable/ai";
export class TextCompletionConfigError extends S.TaggedErrorClass<TextCompletionConfigError>()(
@ -43,15 +44,15 @@ export interface LanguageModelProviderOptions<Requirements> {
readonly provider: string;
readonly defaultModel: string;
readonly defaultTemperature: number;
readonly runtime: ManagedRuntime.ManagedRuntime<Requirements, TextCompletionRuntimeError>;
readonly context: Context.Context<Requirements>;
readonly makeLanguageModel: (
request: LanguageModelProviderRequest,
) => Effect.Effect<LanguageModel.Service, TextCompletionRuntimeError, Requirements>;
}
export const makeTextCompletionLayer = <E, R>(
provider: Effect.Effect<LlmProvider, E, R>,
): Layer.Layer<Llm, E, R> =>
export const makeTextCompletionLayer = <ProviderError, E, R>(
provider: Effect.Effect<LlmProvider<ProviderError>, E, R>,
): Layer.Layer<Llm, E, Exclude<R, Scope.Scope>> =>
Layer.effect(Llm)(
provider.pipe(
Effect.map((resolvedProvider) =>
@ -279,39 +280,25 @@ const languageModelStreamChunk = (
Match.orElse(() => Effect.succeed(Result.fail(undefined))),
);
const runLanguageModelStream = <RuntimeRequirements, StreamRequirements extends RuntimeRequirements>(
runtime: ManagedRuntime.ManagedRuntime<RuntimeRequirements, TextCompletionRuntimeError>,
stream: Stream.Stream<LlmChunk, TextCompletionRuntimeError, StreamRequirements>,
): AsyncIterable<LlmChunk> => ({
[Symbol.asyncIterator]: () => {
const iterator = runtime.context().then((context) =>
Stream.toAsyncIterableWith(stream, context)[Symbol.asyncIterator]()
);
return {
next: () => iterator.then((current) => current.next()),
};
},
});
export const makeLanguageModelProvider = <Requirements>(
options: LanguageModelProviderOptions<Requirements>,
): LlmProvider => ({
): LlmProvider<TextCompletionRuntimeError> => ({
generateContent: (system, prompt, model, temperature) => {
const modelName = model ?? options.defaultModel;
const temp = temperature ?? options.defaultTemperature;
return options.runtime.runPromise(
Effect.gen(function* () {
const languageModel = yield* options.makeLanguageModel({
model: modelName,
temperature: temp,
});
const response = yield* languageModel.generateText({
prompt: languageModelPrompt(system, prompt),
}).pipe(
Effect.mapError((error) => effectAiProviderError(options.provider, error)),
);
return languageModelResult(response, modelName);
}),
return Effect.gen(function* () {
const languageModel = yield* options.makeLanguageModel({
model: modelName,
temperature: temp,
});
const response = yield* languageModel.generateText({
prompt: languageModelPrompt(system, prompt),
}).pipe(
Effect.mapError((error) => effectAiProviderError(options.provider, error)),
);
return languageModelResult(response, modelName);
}).pipe(
Effect.provideContext(options.context),
);
},
supportsStreaming: () => true,
@ -333,30 +320,9 @@ export const makeLanguageModelProvider = <Requirements>(
),
);
}),
).pipe(
Stream.provideContext(options.context),
);
return toAsyncGenerator(runLanguageModelStream(options.runtime, stream), (error) =>
effectAiProviderError(options.provider, error)
);
return stream;
},
});
export const toAsyncGenerator = (
iterable: AsyncIterable<LlmChunk>,
mapError: (error: unknown) => TextCompletionRuntimeError,
): AsyncGenerator<LlmChunk> => {
const iterator = iterable[Symbol.asyncIterator]();
let generator: AsyncGenerator<LlmChunk>;
generator = {
next: (value?: unknown) => iterator.next(value),
return: (value?: unknown) =>
iterator.return === undefined
? Promise.resolve({ done: true, value })
: iterator.return(value),
throw: (error?: unknown) =>
iterator.throw === undefined
? Promise.reject(mapError(error))
: iterator.throw(error),
[Symbol.asyncIterator]: () => generator,
};
return generator;
};

View file

@ -16,9 +16,8 @@ import {
type LlmProvider,
type ProcessorConfig,
type LlmResult,
type LlmChunk,
} from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
import { Effect, Stream } from "effect";
import {
llmStreamPart,
makeTextCompletionLayer,
@ -27,7 +26,6 @@ import {
requiredString,
streamTextCompletionChunks,
textFromContent,
toAsyncGenerator,
type TextCompletionConfigError,
type TextCompletionRuntimeError,
} from "./common.ts";
@ -71,7 +69,7 @@ const mapMistralError = (error: unknown): TextCompletionRuntimeError =>
const makeMistralProviderFromClient = (
resolved: ResolvedMistralConfig,
client: Mistral,
): LlmProvider => {
): LlmProvider<TextCompletionRuntimeError> => {
const {
defaultModel,
defaultTemperature,
@ -84,31 +82,29 @@ const makeMistralProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): Promise<LlmResult> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
return Effect.runPromise(
Effect.tryPromise({
try: () =>
client.chat.complete({
model: modelName,
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
maxTokens: maxOutput,
}),
catch: mapMistralError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: textFromContent(resp.choices?.[0]?.message?.content),
inToken: resp.usage?.promptTokens ?? 0,
outToken: resp.usage?.completionTokens ?? 0,
return Effect.tryPromise({
try: () =>
client.chat.complete({
model: modelName,
})),
),
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
maxTokens: maxOutput,
}),
catch: mapMistralError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: textFromContent(resp.choices?.[0]?.message?.content),
inToken: resp.usage?.promptTokens ?? 0,
outToken: resp.usage?.completionTokens ?? 0,
model: modelName,
})),
);
},
supportsStreaming: () => true,
@ -117,11 +113,11 @@ const makeMistralProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): AsyncGenerator<LlmChunk> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
const stream = Stream.fromEffect(
return Stream.fromEffect(
Effect.tryPromise({
try: () =>
client.chat.stream({
@ -149,13 +145,13 @@ const makeMistralProviderFromClient = (
})
),
);
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapMistralError);
},
} satisfies LlmProvider;
} satisfies LlmProvider<TextCompletionRuntimeError>;
};
export function makeMistralProvider(config: MistralProcessorConfig): LlmProvider {
export function makeMistralProvider(
config: MistralProcessorConfig,
): LlmProvider<TextCompletionRuntimeError> {
return Effect.runSync(makeMistralProviderEffect(config));
}
@ -192,12 +188,6 @@ export const program = makeFlowProcessorProgram<
layer: (config) => makeTextCompletionLayer(makeMistralProviderEffect(config)),
});
const mistralTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return mistralTextCompletionRuntime.runPromise(program);
}
export function runMain(): void {
NodeRuntime.runMain(program);
}

View file

@ -16,16 +16,14 @@ import {
type LlmProvider,
type ProcessorConfig,
type LlmResult,
type LlmChunk,
} from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
import { Effect, Stream } from "effect";
import {
llmStreamPart,
makeTextCompletionLayer,
optionalStringConfig,
providerRuntimeError,
streamTextCompletionChunks,
toAsyncGenerator,
type TextCompletionConfigError,
type TextCompletionRuntimeError,
} from "./common.ts";
@ -59,7 +57,7 @@ const mapOllamaError = (error: unknown): TextCompletionRuntimeError =>
const makeOllamaProviderFromClient = (
resolved: ResolvedOllamaConfig,
client: Ollama,
): LlmProvider => {
): LlmProvider<TextCompletionRuntimeError> => {
const { defaultModel } = resolved;
return {
@ -68,27 +66,25 @@ const makeOllamaProviderFromClient = (
prompt: string,
model?: string,
_temperature?: number,
): Promise<LlmResult> => {
) => {
const modelName = model ?? defaultModel;
const fullPrompt = system + "\n\n" + prompt;
return Effect.runPromise(
Effect.tryPromise({
try: () =>
client.generate({
model: modelName,
prompt: fullPrompt,
stream: false,
}),
catch: mapOllamaError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.response,
inToken: resp.prompt_eval_count ?? 0,
outToken: resp.eval_count ?? 0,
return Effect.tryPromise({
try: () =>
client.generate({
model: modelName,
})),
),
prompt: fullPrompt,
stream: false,
}),
catch: mapOllamaError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.response,
inToken: resp.prompt_eval_count ?? 0,
outToken: resp.eval_count ?? 0,
model: modelName,
})),
);
},
supportsStreaming: () => true,
@ -97,11 +93,11 @@ const makeOllamaProviderFromClient = (
prompt: string,
model?: string,
_temperature?: number,
): AsyncGenerator<LlmChunk> => {
) => {
const modelName = model ?? defaultModel;
const fullPrompt = system + "\n\n" + prompt;
const stream = Stream.fromEffect(
return Stream.fromEffect(
Effect.tryPromise({
try: () =>
client.generate({
@ -125,13 +121,13 @@ const makeOllamaProviderFromClient = (
})
),
);
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOllamaError);
},
} satisfies LlmProvider;
} satisfies LlmProvider<TextCompletionRuntimeError>;
};
export function makeOllamaProvider(config: OllamaProcessorConfig): LlmProvider {
export function makeOllamaProvider(
config: OllamaProcessorConfig,
): LlmProvider<TextCompletionRuntimeError> {
return Effect.runSync(makeOllamaProviderEffect(config));
}
@ -170,12 +166,6 @@ export const program = makeFlowProcessorProgram<
layer: (config) => makeTextCompletionLayer(makeOllamaProviderEffect(config)),
});
const ollamaTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return ollamaTextCompletionRuntime.runPromise(program);
}
export function runMain(): void {
NodeRuntime.runMain(program);
}

View file

@ -19,9 +19,8 @@ import {
type LlmProvider,
type ProcessorConfig,
type LlmResult,
type LlmChunk,
} from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
import { Effect, Stream } from "effect";
import {
llmStreamPart,
makeTextCompletionLayer,
@ -29,7 +28,6 @@ import {
providerStatusError,
requiredString,
streamTextCompletionChunks,
toAsyncGenerator,
type TextCompletionConfigError,
type TextCompletionRuntimeError,
} from "./common.ts";
@ -79,7 +77,7 @@ const mapOpenAICompatibleError = (error: unknown): TextCompletionRuntimeError =>
const makeOpenAICompatibleProviderFromClient = (
resolved: ResolvedOpenAICompatibleConfig,
client: OpenAI,
): LlmProvider => {
): LlmProvider<TextCompletionRuntimeError> => {
const {
defaultModel,
defaultTemperature,
@ -92,31 +90,29 @@ const makeOpenAICompatibleProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): Promise<LlmResult> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
return Effect.runPromise(
Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: modelName,
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
max_tokens: maxOutput,
}),
catch: mapOpenAICompatibleError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.choices[0].message.content ?? "",
inToken: resp.usage?.prompt_tokens ?? 0,
outToken: resp.usage?.completion_tokens ?? 0,
return Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: modelName,
})),
),
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
max_tokens: maxOutput,
}),
catch: mapOpenAICompatibleError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.choices[0].message.content ?? "",
inToken: resp.usage?.prompt_tokens ?? 0,
outToken: resp.usage?.completion_tokens ?? 0,
model: modelName,
})),
);
},
supportsStreaming: () => true,
@ -125,11 +121,11 @@ const makeOpenAICompatibleProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): AsyncGenerator<LlmChunk> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
const stream = Stream.fromEffect(
return Stream.fromEffect(
Effect.tryPromise({
try: () =>
client.chat.completions.create({
@ -158,15 +154,13 @@ const makeOpenAICompatibleProviderFromClient = (
})
),
);
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOpenAICompatibleError);
},
} satisfies LlmProvider;
} satisfies LlmProvider<TextCompletionRuntimeError>;
};
export function makeOpenAICompatibleProvider(
config: OpenAICompatibleProcessorConfig,
): LlmProvider {
): LlmProvider<TextCompletionRuntimeError> {
return Effect.runSync(makeOpenAICompatibleProviderEffect(config));
}
@ -203,12 +197,6 @@ export const program = makeFlowProcessorProgram<
layer: (config) => makeTextCompletionLayer(makeOpenAICompatibleProviderEffect(config)),
});
const openAICompatibleTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return openAICompatibleTextCompletionRuntime.runPromise(program);
}
export function runMain(): void {
NodeRuntime.runMain(program);
}

View file

@ -14,9 +14,8 @@ import {
type LlmProvider,
type ProcessorConfig,
type LlmResult,
type LlmChunk,
} from "@trustgraph/base";
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
import { Effect, Stream } from "effect";
import {
llmStreamPart,
makeTextCompletionLayer,
@ -24,7 +23,6 @@ import {
providerStatusError,
requiredString,
streamTextCompletionChunks,
toAsyncGenerator,
type TextCompletionConfigError,
type TextCompletionRuntimeError,
} from "./common.ts";
@ -68,7 +66,7 @@ const mapOpenAIError = (error: unknown): TextCompletionRuntimeError =>
const makeOpenAIProviderFromClient = (
resolved: ResolvedOpenAIConfig,
client: OpenAI,
): LlmProvider => {
): LlmProvider<TextCompletionRuntimeError> => {
const {
defaultModel,
defaultTemperature,
@ -81,31 +79,29 @@ const makeOpenAIProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): Promise<LlmResult> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
return Effect.runPromise(
Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: modelName,
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
max_completion_tokens: maxOutput,
}),
catch: mapOpenAIError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.choices[0].message.content ?? "",
inToken: resp.usage?.prompt_tokens ?? 0,
outToken: resp.usage?.completion_tokens ?? 0,
return Effect.tryPromise({
try: () =>
client.chat.completions.create({
model: modelName,
})),
),
messages: [
{ role: "system", content: system },
{ role: "user", content: prompt },
],
temperature: temp,
max_completion_tokens: maxOutput,
}),
catch: mapOpenAIError,
}).pipe(
Effect.map((resp): LlmResult => ({
text: resp.choices[0].message.content ?? "",
inToken: resp.usage?.prompt_tokens ?? 0,
outToken: resp.usage?.completion_tokens ?? 0,
model: modelName,
})),
);
},
supportsStreaming: () => true,
@ -114,11 +110,11 @@ const makeOpenAIProviderFromClient = (
prompt: string,
model?: string,
temperature?: number,
): AsyncGenerator<LlmChunk> => {
) => {
const modelName = model ?? defaultModel;
const temp = temperature ?? defaultTemperature;
const stream = Stream.fromEffect(
return Stream.fromEffect(
Effect.tryPromise({
try: () =>
client.chat.completions.create({
@ -148,13 +144,13 @@ const makeOpenAIProviderFromClient = (
})
),
);
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOpenAIError);
},
} satisfies LlmProvider;
} satisfies LlmProvider<TextCompletionRuntimeError>;
};
export function makeOpenAIProvider(config: OpenAIProcessorConfig): LlmProvider {
export function makeOpenAIProvider(
config: OpenAIProcessorConfig,
): LlmProvider<TextCompletionRuntimeError> {
return Effect.runSync(makeOpenAIProviderEffect(config));
}
@ -195,12 +191,6 @@ export const program = makeFlowProcessorProgram<
layer: (config) => makeTextCompletionLayer(makeOpenAIProviderEffect(config)),
});
const openAITextCompletionRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return openAITextCompletionRuntime.runPromise(program);
}
export function runMain(): void {
NodeRuntime.runMain(program);
}