Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@
* - /v1/audio/speech (TTS API)
*/

export const AUDIO_TRANSCRIPTION_PROVIDERS = {
interface AudioModel {
id: string;
name: string;
}

interface AudioProvider {
id: string;
baseUrl: string;
authType: string;
authHeader: string;
format?: string;
async?: boolean;
models: AudioModel[];
}

export const AUDIO_TRANSCRIPTION_PROVIDERS: Record<string, AudioProvider> = {
openai: {
id: "openai",
baseUrl: "https://api.openai.com/v1/audio/transcriptions",
Expand Down Expand Up @@ -57,7 +72,7 @@ export const AUDIO_TRANSCRIPTION_PROVIDERS = {
},
};

export const AUDIO_SPEECH_PROVIDERS = {
export const AUDIO_SPEECH_PROVIDERS: Record<string, AudioProvider> = {
openai: {
id: "openai",
baseUrl: "https://api.openai.com/v1/audio/speech",
Expand Down Expand Up @@ -96,21 +111,21 @@ export const AUDIO_SPEECH_PROVIDERS = {
/**
* Get transcription provider config by ID
*/
export function getTranscriptionProvider(providerId) {
export function getTranscriptionProvider(providerId: string): AudioProvider | null {
return AUDIO_TRANSCRIPTION_PROVIDERS[providerId] || null;
}

/**
* Get speech provider config by ID
*/
export function getSpeechProvider(providerId) {
export function getSpeechProvider(providerId: string): AudioProvider | null {
return AUDIO_SPEECH_PROVIDERS[providerId] || null;
}

/**
* Parse audio model string (format: "provider/model" or just "model")
*/
function parseAudioModel(modelStr, registry) {
function parseAudioModel(modelStr: string | null, registry: Record<string, AudioProvider>): { provider: string | null; model: string | null } {
if (!modelStr) return { provider: null, model: null };

for (const [providerId, config] of Object.entries(registry)) {
Expand All @@ -128,11 +143,11 @@ function parseAudioModel(modelStr, registry) {
return { provider: null, model: modelStr };
}

export function parseTranscriptionModel(modelStr) {
export function parseTranscriptionModel(modelStr: string | null) {
return parseAudioModel(modelStr, AUDIO_TRANSCRIPTION_PROVIDERS);
}

export function parseSpeechModel(modelStr) {
export function parseSpeechModel(modelStr: string | null) {
return parseAudioModel(modelStr, AUDIO_SPEECH_PROVIDERS);
}

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,51 @@
* is auto-generated from this registry.
*/

// ── Types ─────────────────────────────────────────────────────────────────

export interface RegistryModel {
id: string;
name: string;
targetFormat?: string;
}

export interface RegistryOAuth {
clientIdEnv?: string;
clientIdDefault?: string;
clientSecretEnv?: string;
clientSecretDefault?: string;
tokenUrl?: string;
refreshUrl?: string;
authUrl?: string;
initiateUrl?: string;
pollUrlBase?: string;
}

export interface RegistryEntry {
id: string;
alias: string;
format: string;
executor: string;
baseUrl?: string;
baseUrls?: string[];
responsesBaseUrl?: string;
urlSuffix?: string;
urlBuilder?: (base: string, model: string, stream: boolean) => string;
authType: string;
authHeader: string;
authPrefix?: string;
headers?: Record<string, string>;
extraHeaders?: Record<string, string>;
oauth?: RegistryOAuth;
models: RegistryModel[];
chatPath?: string;
clientVersion?: string;
passthroughModels?: boolean;
}

// ── Registry ──────────────────────────────────────────────────────────────

export const REGISTRY = {
export const REGISTRY: Record<string, RegistryEntry> = {
// ─── OAuth Providers ───────────────────────────────────────────────────
claude: {
id: "claude",
Expand Down Expand Up @@ -760,10 +802,10 @@ export const REGISTRY = {
// ── Generator Functions ───────────────────────────────────────────────────

/** Generate legacy PROVIDERS object shape for constants.js backward compatibility */
export function generateLegacyProviders() {
const providers = {};
export function generateLegacyProviders(): Record<string, any> {
const providers: Record<string, any> = {};
for (const [id, entry] of Object.entries(REGISTRY)) {
const p = { format: entry.format };
const p: Record<string, any> = { format: entry.format };

// URL(s)
if (entry.baseUrls) {
Expand Down Expand Up @@ -808,8 +850,8 @@ export function generateLegacyProviders() {
}

/** Generate PROVIDER_MODELS map (alias → model list) */
export function generateModels() {
const models = {};
export function generateModels(): Record<string, RegistryModel[]> {
const models: Record<string, RegistryModel[]> = {};
for (const entry of Object.values(REGISTRY)) {
if (entry.models && entry.models.length > 0) {
const key = entry.alias || entry.id;
Expand All @@ -823,8 +865,8 @@ export function generateModels() {
}

/** Generate PROVIDER_ID_TO_ALIAS map */
export function generateAliasMap() {
const map = {};
export function generateAliasMap(): Record<string, string> {
const map: Record<string, string> = {};
for (const entry of Object.values(REGISTRY)) {
map[entry.id] = entry.alias || entry.id;
}
Expand All @@ -841,12 +883,12 @@ for (const entry of Object.values(REGISTRY)) {
}

/** Get registry entry by provider ID or alias */
export function getRegistryEntry(provider) {
export function getRegistryEntry(provider: string): RegistryEntry | null {
return REGISTRY[provider] || _byAlias.get(provider) || null;
}

/** Get all registered provider IDs */
export function getRegisteredProviders() {
export function getRegisteredProviders(): string[] {
return Object.keys(REGISTRY);
}

Expand All @@ -856,7 +898,7 @@ export function getRegisteredProviders() {
* @param {string} provider - Provider ID or alias
* @returns {"oauth"|"apikey"}
*/
export function getProviderCategory(provider) {
export function getProviderCategory(provider: string): "oauth" | "apikey" {
const entry = getRegistryEntry(provider);
if (!entry) return "apikey"; // Safe default for unknown providers
return entry.authType === "apikey" ? "apikey" : "oauth";
Expand Down
File renamed without changes.
File renamed without changes.
7 changes: 5 additions & 2 deletions open-sse/executors/base.js → open-sse/executors/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.js";
* (buildUrl, buildHeaders, transformRequest, etc.) for each provider.
*/
export class BaseExecutor {
constructor(provider, config) {
provider: any;
config: any;

constructor(provider: any, config: any) {
Comment on lines +9 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While using any is a quick way to migrate to TypeScript, it sacrifices type safety. Consider defining base interfaces for provider and config and using generics to allow subclasses to provide more specific types. This would improve type safety and autocompletion throughout the codebase.

For example:

interface BaseProviderConfig {
  // common config properties
}

export class BaseExecutor<C extends BaseProviderConfig> {
  provider: any; // or a more specific base type
  config: C;

  constructor(provider: any, config: C) {
    this.provider = provider;
    this.config = config;
  }
  // ...
}

this.provider = provider;
this.config = config;
}
Expand Down Expand Up @@ -96,7 +99,7 @@ export class BaseExecutor {
? AbortSignal.any([signal, timeoutSignal])
: signal || timeoutSignal;

const fetchOptions = {
const fetchOptions: Record<string, any> = {
method: "POST",
headers,
body: JSON.stringify(transformedBody),
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
declare var EdgeRuntime: any;
/**
* CursorExecutor — Handles communication with the Cursor IDE API.
*
Expand Down Expand Up @@ -226,7 +227,7 @@ export class CursorExecutor extends BaseExecutor {

return {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
headers: Object.fromEntries((response.headers as any).entries()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The as any cast here hides potential type issues. The Headers object is iterable, and Object.fromEntries accepts an iterable of key-value pairs. This should work without any in modern environments. If you are facing type compatibility issues between different environments (Node vs. Edge), you could create a helper function to convert headers to an object in a type-safe way, but simply removing the cast is preferable if it works.

      headers: Object.fromEntries(response.headers.entries()),

body: Buffer.from(await response.arrayBuffer()),
};
}
Expand Down Expand Up @@ -290,7 +291,7 @@ export class CursorExecutor extends BaseExecutor {
const transformedBody = this.transformRequest(model, body, stream, credentials);

try {
const response = http2
const response: any = http2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using any for the response variable defeats the purpose of TypeScript. It seems makeHttp2Request and makeFetchRequest return a similar object structure. It would be beneficial to define an interface for this response shape and use it here to ensure type safety.

For example:

interface CustomResponse {
  status: number;
  headers: Record<string, string | number | string[]>;
  body: Buffer;
}

// ...

const response: CustomResponse = http2
  ? await this.makeHttp2Request(url, headers, transformedBody, signal)
  : await this.makeFetchRequest(url, headers, transformedBody, signal);

? await this.makeHttp2Request(url, headers, transformedBody, signal)
: await this.makeFetchRequest(url, headers, transformedBody, signal);

Expand Down Expand Up @@ -458,8 +459,7 @@ export class CursorExecutor extends BaseExecutor {

console.log(`[CURSOR BUFFER] Final toolCalls count: ${toolCalls.length}`);

const message = {
role: "assistant",
const message: Record<string, any> = { role: "assistant",
content: totalContent || null,
};

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
11 changes: 5 additions & 6 deletions open-sse/executors/kiro.js → open-sse/executors/kiro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ export class KiroExecutor extends BaseExecutor {
let chunkIndex = 0;
const responseId = `chatcmpl-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
const state = {
endDetected: false,
const state: Record<string, any> = { endDetected: false,
finishEmitted: false,
hasToolCalls: false,
toolCallIndex: 0,
Expand Down Expand Up @@ -126,7 +125,7 @@ export class KiroExecutor extends BaseExecutor {
const content = event.payload.content;
state.totalContentLength += content.length;

const chunk = {
const chunk: Record<string, any> = {
id: responseId,
object: "chat.completion.chunk",
created,
Expand All @@ -145,7 +144,7 @@ export class KiroExecutor extends BaseExecutor {

// Handle codeEvent
if (eventType === "codeEvent" && event.payload?.content) {
const chunk = {
const chunk: Record<string, any> = {
id: responseId,
object: "chat.completion.chunk",
created,
Expand Down Expand Up @@ -257,7 +256,7 @@ export class KiroExecutor extends BaseExecutor {

// Handle messageStopEvent
if (eventType === "messageStopEvent") {
const chunk = {
const chunk: Record<string, any> = {
id: responseId,
object: "chat.completion.chunk",
created,
Expand Down Expand Up @@ -330,7 +329,7 @@ export class KiroExecutor extends BaseExecutor {
};
}

const finishChunk = {
const finishChunk: Record<string, any> = {
id: responseId,
object: "chat.completion.chunk",
created,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,27 +100,28 @@ async function handleDeepgramSpeech(providerConfig, body, modelId, token) {
* @param {Object} options.credentials - Provider credentials { apiKey }
* @returns {Response}
*/
/** @returns {Promise<any>} */
export async function handleAudioSpeech({ body, credentials }) {
if (!body.model) {
return errorResponse("model is required", 400);
return errorResponse(400, "model is required");
}
if (!body.input) {
return errorResponse("input is required", 400);
return errorResponse(400, "input is required");
}

const { provider: providerId, model: modelId } = parseSpeechModel(body.model);
const providerConfig = providerId ? getSpeechProvider(providerId) : null;

if (!providerConfig) {
return errorResponse(
`No speech provider found for model "${body.model}". Available: openai, hyperbolic, deepgram`,
400
400,
`No speech provider found for model "${body.model}". Available: openai, hyperbolic, deepgram`
);
}

const token = credentials?.apiKey || credentials?.accessToken;
if (!token) {
return errorResponse(`No credentials for speech provider: ${providerId}`, 401);
return errorResponse(401, `No credentials for speech provider: ${providerId}`);
}

try {
Expand Down Expand Up @@ -168,6 +169,6 @@ export async function handleAudioSpeech({ body, credentials }) {
},
});
} catch (err) {
return errorResponse(`Speech request failed: ${err.message}`, 500);
return errorResponse(500, `Speech request failed: ${err.message}`);
}
}
Loading