Skip to content
Open
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
5 changes: 4 additions & 1 deletion packages/core/src/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { llmgateway } from "./providers/llmgateway.js";
import { openai } from "./providers/openai.js";
import { openrouter } from "./providers/openrouter.js";
import { ovhcloud } from "./providers/ovhcloud.js";
import { tzafon } from "./providers/tzafon.js";
import { vercel } from "./providers/vercel.js";
import { venice } from "./providers/venice.js";
import { xai } from "./providers/xai.js";
Expand Down Expand Up @@ -99,6 +100,7 @@ export const providers: {
openai: SyncProvider<any>;
openrouter: SyncProvider<any>;
ovhcloud: SyncProvider<any>;
tzafon: SyncProvider<any>;
vercel: SyncProvider<any>;
venice: SyncProvider<any>;
xai: SyncProvider<any>;
Expand All @@ -115,6 +117,7 @@ export const providers: {
openai,
openrouter,
ovhcloud,
tzafon,
vercel,
venice,
xai,
Expand All @@ -123,7 +126,7 @@ export const providers: {
export const groups = {
aggregators: ["huggingface", "llmgateway", "openrouter", "vercel"],
cloudflare: ["cloudflare-workers-ai"],
direct: ["anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "openai", "ovhcloud", "venice", "xai"],
direct: ["anthropic", "baseten", "chutes", "deepinfra", "digitalocean", "google", "openai", "ovhcloud", "tzafon", "venice", "xai"],
} as const;

type ProviderID = keyof typeof providers;
Expand Down
80 changes: 80 additions & 0 deletions packages/core/src/sync/providers/tzafon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { z } from "zod";

import type { SyncProvider } from "../index.js";

const API_ENDPOINT = "https://api.tzafon.ai/v1/models";

const TzafonModel = z.object({
id: z.string(),
created: z.number().int().nonnegative(),
object: z.literal("model"),
features: z.array(z.string()).optional(),
}).passthrough();

const TzafonResponse = z.object({
data: z.array(TzafonModel),
});

export type TzafonModel = z.infer<typeof TzafonModel>;

// The listing is key-scoped: an admin key also returns internal `tzafon.internal.*`
// checkpoints that regular users can never reach. Only `tzafon.*` public IDs may enter
// the catalog, regardless of which key ran the sync.
function isPublic(id: string) {
return id.startsWith("tzafon.");
}

export const tzafon = {
id: "tzafon",
name: "Tzafon",
modelsDir: "providers/tzafon/models",
skipCreates: true,
deleteMissing: false,
sourceID(model) {
return model.id;
},
skippedNotice(ids) {
if (ids.length === 0) return [];
return [
`${ids.length} Tzafon models returned by \`/v1/models\` were not created because the endpoint does not provide authoritative pricing, limits, or modalities for the catalog. Existing models are still updated from API-authoritative fields (\`created\`).`,
`Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`,
];
},
async fetchModels() {
// TZAFON_API_KEY is optional here: an unauthenticated/user-scoped listing is
// already public-only. If a key is set it must be a regular-user key — an admin
// key must never widen the synced set, which isPublic() enforces regardless.
const headers: Record<string, string> = {};
if (process.env.TZAFON_API_KEY) headers.Authorization = `Bearer ${process.env.TZAFON_API_KEY}`;

const response = await fetch(API_ENDPOINT, { headers });
if (!response.ok) {
throw new Error(`Tzafon models request failed: ${response.status} ${response.statusText}`);
}
return response.json();
},
parseModels(raw) {
return TzafonResponse.parse(raw).data.filter((model) => isPublic(model.id));
},
translateModel(model, context) {
const existing = context.existing(model.id);
// Never auto-create: /v1/models only exposes id/created/features, which isn't
// enough to author a compliant catalog entry (cost, limits, modalities are all
// hand-verified against live probes). New IDs surface via skippedNotice instead.
if (existing === undefined) return undefined;

return {
id: model.id,
model: {
...existing,
// `created` is the only field the API is authoritative for; keep any
// hand-authored release_date rather than overwriting it.
release_date: existing.release_date ?? isoDate(model.created),
},
};
},
} satisfies SyncProvider<TzafonModel>;

function isoDate(unixSeconds: number) {
return new Date(unixSeconds * 1000).toISOString().slice(0, 10);
}
3 changes: 3 additions & 0 deletions providers/tzafon/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions providers/tzafon/models/tzafon.northstar-cua-fast-1.6.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name = "Northstar CUA Fast 1.6"
description = "Updated computer-use agent model for browser and desktop automation via screenshot input"
attachment = true
reasoning = false
tool_call = true
structured_output = true
temperature = true
release_date = "2026-05-09" # /v1/models created timestamp
last_updated = "2026-05-09"
open_weights = false

[cost]
# Priced the same as tzafon.northstar-cua-fast (not yet broken out separately on
# https://docs.lightcone.ai/guides/pricing/ as of 2026-07-02).
input = 0.50
output = 1.50

[limit]
# probed 2026-07-02: max_model_len=max_total_tokens=131072
context = 131_072
input = 131_072
output = 131_072

[modalities]
input = ["text", "image"]
output = ["text"]
26 changes: 26 additions & 0 deletions providers/tzafon/models/tzafon.northstar-cua-fast.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name = "Northstar CUA Fast"
description = "Computer-use agent model for browser and desktop automation via screenshot input"
attachment = true # accepts screenshot/image input
reasoning = false
tool_call = true
structured_output = true # response_format: json_object / json schema
temperature = true
release_date = "2026-04-22" # /v1/models created timestamp
last_updated = "2026-07-02"
open_weights = false

[cost]
# https://docs.lightcone.ai/guides/pricing/ — "input is now $0.50/M (was $1.00) and
# output is $1.50/M (was $5.00) — a 50-70% reduction"
input = 0.50
output = 1.50

[limit]
# probed 2026-07-02: max_model_len=max_total_tokens=262144 (shared input+output budget)
context = 262_144
input = 262_144
output = 262_144

[modalities]
input = ["text", "image"]
output = ["text"]
5 changes: 5 additions & 0 deletions providers/tzafon/provider.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name = "Tzafon"
npm = "@ai-sdk/openai-compatible"
api = "https://api.tzafon.ai/v1"
env = ["TZAFON_API_KEY"]
doc = "https://docs.lightcone.ai/guides/pricing/"
Loading