ServicesAI Services

Call a model

Call Federation models with ModelCall, CityModel, and ModelStreamEvent.

Call a Federation model

const catalog = await city.ai.catalog();
const model = catalog.require("deepseek-v4-flash");
const stream = await model.stream({
  messages: [{
    role: "user",
    content: [{ type: "text", text: "Explain this repository" }],
  }],
});

for await (const event of stream) {
  if (event.type === "text_delta") process.stdout.write(event.delta);
}

model.stream() returns a ReadableStream<ModelStreamEvent>. A successful stream begins with model_start and provides final model_usage before model_finish. Invalid event ordering fails as a protocol error.

Use a model with Agent

Objects returned by the catalog implement ModelClient and can be passed directly to Agent:

const catalog = await city.ai.catalog();
const model = catalog.require("deepseek-v4-flash");
const agent = new Agent({ id: "reviewer", model });

Agent owns Session context, tool loops, and approvals. Federation owns final model selection and billing.

Multimodal fallback

Fallback checks file content only in the latest role: "user" message in ModelCall.messages. A new text-only user message does not trigger fallback again. When the final model's fallback rules reject a historical file, the execution view replaces that file with its filename, original URL, or MIME type as plain text. The original Session file remains unchanged. Trailing assistant or tool messages do not change which user message is inspected.

OpenAI-compatible clients

Use /v1/ai/chat/completions for the OpenAI SDK, curl, or an existing gateway. The boundary adapter converts that request into the same ModelCall, so routing, fallback, reasoning, usage, and billing stay consistent.