Downcity
Downcity City DocsServicesAI Services

Provider & Model Registration

Provider constructor, model(), handler signatures, and ModelConfig fields.

Provider Constructor

new Provider(id: string, opts: ProviderOptions)

ProviderOptions

FieldTypeRequiredDescription
envRecord<string, string>NoEnv var key → description
baseURLstringNoProvider API URL, for auto-passthrough
envKeystringNoEnv var name for API key, for auto-passthrough
textServiceHandlerYesSDK text handler
streamServiceHandlerYesSDK stream handler
imageServiceHandlerNoSDK image handler
videoServiceHandlerNoSDK video handler
openaiServiceHandlerNoOpenAI-compatible handler. Auto-passthrough if omitted

Two Patterns

Pattern 1: OpenAI-Compatible (Auto Passthrough)

Provide baseURL + envKey, skip openai:

const deepseek = new Provider("deepseek", {
  baseURL: "https://api.deepseek.com",
  envKey: "DEEPSEEK_API_KEY",
  text: myTextHandler,
  stream: myStreamHandler,
  // no openai → auto-passthrough
});

Pattern 2: Non-OpenAI Format (Write openai Handler)

Must provide openai handler for format conversion:

const kimiCode = new Provider("kimi-code", {
  env: { KIMI_CODE_API_KEY: "Kimi Code API Key" },
  text: myTextHandler,
  stream: myStreamHandler,
  openai: async (ctx) => {
    // ctx.input is standard OpenAI body
    // Downstream: OpenAI → Provider format
    // Upstream: Provider response → OpenAI format
  },
});

Handler Signatures

type ServiceHandler = (ctx: Context) => unknown | Response | Promise<unknown | Response>;
Pathwayctx.inputRecommended Return
text{ prompt }UIMessage
stream{ prompt }Response (SSE) or UIMessageStream
openai{ model, messages, stream, tools, ... }Response

model() Method

provider.model(spec): ModelConfig

spec

FieldTypeRequired
idstringYes
namestringYes
descriptionstringNo
tagsstring[]No
metaRecord<string, unknown>No
defaultboolean | string[]No

Registration

const ai = new AIService();
ai.use(
  deepseek.model({ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", default: true }),
);
base.use(ai);