release: WRNexusJS 0.4.0

This commit is contained in:
2026-07-27 12:42:18 +05:30
parent 8b728a3e5d
commit 30e5721e84
250 changed files with 10065 additions and 3923 deletions
+2
View File
@@ -245,3 +245,5 @@ export function createAI(config: AIConfig = {}): AI {
return { generate, stream, streamResponse };
}
export { anthropicProvider, aiProvider, createAIClient } from "./providers.ts";
export type { AIUsage, AIResult, AIProvider, AIClient, AIClientOptions } from "./providers.ts";
+150
View File
@@ -0,0 +1,150 @@
import {
AIError,
createAI,
type AI,
type AIConfig,
type GenerateOptions,
type Message,
} from "./index.ts";
export interface AIUsage {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
}
export interface AIResult<T = string> {
value: T;
provider: string;
model?: string;
usage?: AIUsage;
finishReason?: string;
raw?: unknown;
}
export interface AIProvider {
name: string;
generate(prompt: string | Message[], options?: GenerateOptions): Promise<AIResult<string>>;
stream?(
prompt: string | Message[],
options?: GenerateOptions,
): AsyncGenerator<string, void, unknown>;
}
export interface AIClientOptions {
providers: AIProvider[];
fallback?: boolean;
onAttempt?: (provider: string, error?: unknown) => void | Promise<void>;
}
export interface AIClient {
generate(
prompt: string | Message[],
options?: GenerateOptions & { provider?: string },
): Promise<AIResult<string>>;
generateObject<T>(
prompt: string | Message[],
options?: GenerateOptions & { provider?: string; validate?: (value: unknown) => value is T },
): Promise<AIResult<T>>;
stream(
prompt: string | Message[],
options?: GenerateOptions & { provider?: string },
): AsyncGenerator<string, void, unknown>;
}
export function anthropicProvider(config: AIConfig = {}): AIProvider {
const client = createAI(config);
return {
name: "anthropic",
async generate(prompt: string | Message[], options?: GenerateOptions) {
return {
value: await client.generate(prompt, options),
provider: "anthropic",
model: options?.model ?? config.model,
};
},
stream: (prompt: string | Message[], options?: GenerateOptions) =>
client.stream(prompt, options),
};
}
export function aiProvider(name: string, client: AI): AIProvider {
return {
name,
async generate(prompt: string | Message[], options?: GenerateOptions) {
return {
value: await client.generate(prompt, options),
provider: name,
model: options?.model,
};
},
stream: (prompt: string | Message[], options?: GenerateOptions) =>
client.stream(prompt, options),
};
}
function jsonText(value: string): string {
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(value);
return (fenced?.[1] ?? value).trim();
}
export function createAIClient(options: AIClientOptions): AIClient {
if (!options.providers.length) throw new Error("WRN-AI-NO-PROVIDERS");
const select = (name?: string) =>
name ? options.providers.filter((provider) => provider.name === name) : options.providers;
const generate: AIClient["generate"] = async (prompt, callOptions = {}) => {
const providers = select(callOptions.provider);
if (!providers.length)
throw new AIError(`Unknown AI provider: ${callOptions.provider}`, 0, "provider_error");
let last: unknown;
for (const provider of providers) {
try {
await options.onAttempt?.(provider.name);
return await provider.generate(prompt, callOptions);
} catch (error) {
last = error;
await options.onAttempt?.(provider.name, error);
if (options.fallback === false || callOptions.provider) throw error;
}
}
throw last;
};
return {
generate,
async generateObject<T>(
prompt: string | Message[],
callOptions: GenerateOptions & {
provider?: string;
validate?: (value: unknown) => value is T;
} = {},
) {
const result = await generate(prompt, callOptions);
let value: unknown;
try {
value = JSON.parse(jsonText(result.value));
} catch {
throw new AIError("AI response was not valid JSON", 0, "structured_output_error");
}
if (callOptions.validate && !callOptions.validate(value))
throw new AIError(
"AI response failed structured output validation",
0,
"structured_output_error",
);
return { ...result, value: value as T };
},
async *stream(prompt, callOptions = {}) {
const providers = select(callOptions.provider);
let last: unknown;
for (const provider of providers) {
if (!provider.stream) continue;
try {
yield* provider.stream(prompt, callOptions);
return;
} catch (error) {
last = error;
if (options.fallback === false || callOptions.provider) throw error;
}
}
if (last) throw last;
const result = await generate(prompt, callOptions);
yield result.value;
},
};
}