@oh-my-pi/pi-catalog 18.2.8 → 18.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/compat/axes.d.ts +2 -2
- package/dist/types/compat/catalog-policy.d.ts +7 -0
- package/dist/types/compat/output-limits.d.ts +3 -0
- package/dist/types/compat/provider-ids.d.ts +1 -1
- package/dist/types/compat/tools.d.ts +5 -0
- package/dist/types/provider-models/openai-compat.d.ts +26 -0
- package/dist/types/wire/singularityapi.d.ts +29 -0
- package/package.json +4 -4
- package/src/compat/auth-ids.ts +4 -0
- package/src/compat/axes.ts +21 -2
- package/src/compat/catalog-policy.ts +28 -0
- package/src/compat/output-limits.ts +12 -0
- package/src/compat/provider-ids.ts +2 -0
- package/src/compat/resolve.ts +4 -2
- package/src/compat/rules/README.md +10 -10
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/singularityapi-dev.kdl +22 -0
- package/src/compat/rules/auth/singularityapi-tech.kdl +23 -0
- package/src/compat/rules/classes/gpt-oss.kdl +3 -0
- package/src/compat/rules/classes/xai.kdl +3 -3
- package/src/compat/rules/providers/amazon-bedrock.kdl +1 -1
- package/src/compat/rules/providers/anthropic.kdl +1 -1
- package/src/compat/rules/providers/cloudflare-ai-gateway.kdl +1 -1
- package/src/compat/rules/providers/commandcode.kdl +1 -1
- package/src/compat/rules/providers/cursor.kdl +1 -1
- package/src/compat/rules/providers/google-antigravity.kdl +5 -0
- package/src/compat/rules/providers/kilo.kdl +1 -1
- package/src/compat/rules/providers/litellm.kdl +1 -1
- package/src/compat/rules/providers/native-tools.kdl +4 -0
- package/src/compat/rules/providers/opencode-zen.kdl +1 -1
- package/src/compat/rules/providers/output-limits.kdl +4 -0
- package/src/compat/rules/providers/singularityapi-dev.kdl +92 -0
- package/src/compat/rules/providers/singularityapi-tech.kdl +72 -0
- package/src/compat/rules/providers/tool-free-history.kdl +6 -0
- package/src/compat/rules/providers/vercel-ai-gateway.kdl +1 -1
- package/src/compat/rules/providers/xai-oauth.kdl +8 -1
- package/src/compat/rules/providers/xai.kdl +4 -4
- package/src/compat/rules/providers/xiaomi-token-plan-cn.kdl +23 -0
- package/src/compat/rules/providers/zenmux.kdl +1 -1
- package/src/compat/rules/runtime/behavior.kdl +2 -0
- package/src/compat/rules.json +1 -12526
- package/src/compat/tools.ts +12 -0
- package/src/discovery/devin.ts +59 -36
- package/src/models.json +1 -1
- package/src/provider-models/cache-provider-id.ts +31 -0
- package/src/provider-models/descriptors.ts +4 -0
- package/src/provider-models/openai-compat.ts +159 -0
- package/src/wire/singularityapi.ts +34 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { CHARM_HYPER_API_BASE_URL, normalizeCharmHyperBaseUrl } from "../wire/charm-hyper";
|
|
2
2
|
import { PERSONAL_GITHUB_COPILOT_BASE_URL } from "../wire/github-copilot";
|
|
3
|
+
import {
|
|
4
|
+
SINGULARITYAPI_DEV_API_BASE_URL,
|
|
5
|
+
SINGULARITYAPI_TECH_API_BASE_URL,
|
|
6
|
+
normalizeSingularityApiBaseUrl,
|
|
7
|
+
} from "../wire/singularityapi";
|
|
3
8
|
|
|
4
9
|
export interface ModelCacheProviderIdOptions {
|
|
5
10
|
apiKey?: string;
|
|
@@ -11,6 +16,11 @@ const CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS: Readonly<Record<string, true>> =
|
|
|
11
16
|
"opencode-zen": true,
|
|
12
17
|
"github-copilot": true,
|
|
13
18
|
"muse-code": true,
|
|
19
|
+
// Both SingularityAPI rosters are issued per key, so the namespace must be
|
|
20
|
+
// resolved with the credential (`hydrateCredentialScopedModelCaches`) rather
|
|
21
|
+
// than from the synchronous, credential-less startup read.
|
|
22
|
+
"singularityapi-dev": true,
|
|
23
|
+
"singularityapi-tech": true,
|
|
14
24
|
};
|
|
15
25
|
|
|
16
26
|
/** Whether a provider's model-cache namespace requires its resolved credential. */
|
|
@@ -87,6 +97,27 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
|
|
|
87
97
|
const scope = `${options.apiKey ?? ""}\u0000${baseUrl}`;
|
|
88
98
|
return `muse-code:models-v1:${Bun.hash(scope).toString(36)}`;
|
|
89
99
|
}
|
|
100
|
+
case "singularityapi-dev":
|
|
101
|
+
case "singularityapi-tech": {
|
|
102
|
+
// Both products issue their roster per key, and a configured proxy
|
|
103
|
+
// publishes its own. Discovery is authoritative, so a shared namespace
|
|
104
|
+
// would serve the previous key's roster for the full 24h TTL — including
|
|
105
|
+
// ids the current key cannot call. Hashing the pair means switching
|
|
106
|
+
// either re-runs discovery instead, and the provider-id prefix keeps the
|
|
107
|
+
// two products from ever reading each other's rows behind one proxy.
|
|
108
|
+
//
|
|
109
|
+
// Both call paths must land on one namespace: `ModelRegistry` resolves
|
|
110
|
+
// this provider through the credential-scoped hydration pass (it is in
|
|
111
|
+
// CREDENTIAL_SCOPED_MODEL_CACHE_PROVIDERS), while discovery hashes the
|
|
112
|
+
// `/v1`-suffixed endpoint the matching `singularityApi*ModelManagerOptions`
|
|
113
|
+
// passes — which is why both normalize through
|
|
114
|
+
// `normalizeSingularityApiBaseUrl` against their own canonical host.
|
|
115
|
+
const canonical =
|
|
116
|
+
providerId === "singularityapi-tech" ? SINGULARITYAPI_TECH_API_BASE_URL : SINGULARITYAPI_DEV_API_BASE_URL;
|
|
117
|
+
const baseUrl = normalizeSingularityApiBaseUrl(options.baseUrl, canonical);
|
|
118
|
+
const scope = `${options.apiKey ?? ""}\u0000${baseUrl}`;
|
|
119
|
+
return `${providerId}:models-v1:${Bun.hash(scope).toString(36)}`;
|
|
120
|
+
}
|
|
90
121
|
case "litellm": {
|
|
91
122
|
const baseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
|
|
92
123
|
// rich-v11 invalidates rows that inherited ClinePass gateway metadata
|
|
@@ -59,6 +59,8 @@ import {
|
|
|
59
59
|
sakanaModelManagerOptions,
|
|
60
60
|
siliconflowCnModelManagerOptions,
|
|
61
61
|
siliconflowModelManagerOptions,
|
|
62
|
+
singularityApiDevModelManagerOptions,
|
|
63
|
+
singularityApiTechModelManagerOptions,
|
|
62
64
|
syntheticModelManagerOptions,
|
|
63
65
|
togetherModelManagerOptions,
|
|
64
66
|
umansModelManagerOptions,
|
|
@@ -137,6 +139,8 @@ const MODEL_MANAGER_FACTORIES: Readonly<Partial<Record<KnownProvider, ModelManag
|
|
|
137
139
|
sakana: config => sakanaModelManagerOptions(config),
|
|
138
140
|
siliconflow: config => siliconflowModelManagerOptions(config),
|
|
139
141
|
"siliconflow-cn": config => siliconflowCnModelManagerOptions(config),
|
|
142
|
+
"singularityapi-dev": config => singularityApiDevModelManagerOptions(config),
|
|
143
|
+
"singularityapi-tech": config => singularityApiTechModelManagerOptions(config),
|
|
140
144
|
synthetic: config => syntheticModelManagerOptions(config),
|
|
141
145
|
together: config => togetherModelManagerOptions(config),
|
|
142
146
|
typesafe: config => typesafeModelManagerOptions(config),
|
|
@@ -55,6 +55,11 @@ import {
|
|
|
55
55
|
mergeCopilotApiHeaders,
|
|
56
56
|
parseGitHubCopilotApiKey,
|
|
57
57
|
} from "../wire/github-copilot";
|
|
58
|
+
import {
|
|
59
|
+
SINGULARITYAPI_DEV_API_BASE_URL,
|
|
60
|
+
SINGULARITYAPI_TECH_API_BASE_URL,
|
|
61
|
+
normalizeSingularityApiBaseUrl,
|
|
62
|
+
} from "../wire/singularityapi";
|
|
58
63
|
import { createBundledReferenceMap, createReferenceResolver, toModelSpec } from "./bundled-references";
|
|
59
64
|
import { getDefaultModelDiscoveryBaseUrl, resolveModelCacheProviderId } from "./cache-provider-id";
|
|
60
65
|
import { getClinePassModelMetadata } from "./cline-pass";
|
|
@@ -5048,6 +5053,9 @@ export function xiaomiModelManagerOptions(
|
|
|
5048
5053
|
// would incorrectly pin to the standard endpoint (api.xiaomimimo.com).
|
|
5049
5054
|
const baseUrl = isTokenPlanKey ? tokenPlanBaseUrls[0] : (config?.baseUrl ?? XIAOMI_STANDARD_BASE_URL);
|
|
5050
5055
|
const references = createBundledReferenceMap<"openai-completions">("xiaomi");
|
|
5056
|
+
for (const seed of seedModels<"openai-completions">(providerId)) {
|
|
5057
|
+
references.set(seed.id, seed);
|
|
5058
|
+
}
|
|
5051
5059
|
const fetchModels = (url: string) =>
|
|
5052
5060
|
fetchOpenAICompatibleModels({
|
|
5053
5061
|
api: "openai-completions",
|
|
@@ -7400,3 +7408,154 @@ export function charmHyperModelManagerOptions(
|
|
|
7400
7408
|
}),
|
|
7401
7409
|
};
|
|
7402
7410
|
}
|
|
7411
|
+
|
|
7412
|
+
// ---------------------------------------------------------------------------
|
|
7413
|
+
// SingularityAPI
|
|
7414
|
+
// ---------------------------------------------------------------------------
|
|
7415
|
+
|
|
7416
|
+
export interface SingularityApiModelManagerConfig {
|
|
7417
|
+
apiKey?: string;
|
|
7418
|
+
baseUrl?: string;
|
|
7419
|
+
fetch?: FetchImpl;
|
|
7420
|
+
}
|
|
7421
|
+
|
|
7422
|
+
interface SingularityApiCapability extends Record<string, unknown> {
|
|
7423
|
+
endpoint?: unknown;
|
|
7424
|
+
context_window_tokens?: unknown;
|
|
7425
|
+
maximum_output_tokens?: unknown;
|
|
7426
|
+
default_output_tokens?: unknown;
|
|
7427
|
+
pricing?: unknown;
|
|
7428
|
+
}
|
|
7429
|
+
|
|
7430
|
+
/** Endpoints that decide which transport serves a `/v1/models` row. */
|
|
7431
|
+
const SINGULARITYAPI_CHAT_ENDPOINT = "/v1/chat/completions";
|
|
7432
|
+
const SINGULARITYAPI_IMAGE_ENDPOINT = "/v1/images/generations";
|
|
7433
|
+
|
|
7434
|
+
function singularityApiCapabilities(entry: OpenAICompatibleModelRecord): readonly SingularityApiCapability[] {
|
|
7435
|
+
const capabilities = entry.capabilities;
|
|
7436
|
+
if (!Array.isArray(capabilities)) return [];
|
|
7437
|
+
return capabilities.filter((capability): capability is SingularityApiCapability => isRecord(capability));
|
|
7438
|
+
}
|
|
7439
|
+
|
|
7440
|
+
function toSingularityApiRate(value: unknown): number {
|
|
7441
|
+
const parsed = toNumber(value);
|
|
7442
|
+
return parsed !== undefined && parsed >= 0 ? parsed : 0;
|
|
7443
|
+
}
|
|
7444
|
+
|
|
7445
|
+
function resolveSingularityApiCost(capability: SingularityApiCapability | undefined): ModelSpec<Api>["cost"] {
|
|
7446
|
+
const pricing = capability !== undefined && isRecord(capability.pricing) ? capability.pricing : undefined;
|
|
7447
|
+
if (!pricing) return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
7448
|
+
return {
|
|
7449
|
+
input: toSingularityApiRate(pricing.input_per_million_usd),
|
|
7450
|
+
output: toSingularityApiRate(pricing.output_per_million_usd),
|
|
7451
|
+
cacheRead: 0,
|
|
7452
|
+
cacheWrite: 0,
|
|
7453
|
+
};
|
|
7454
|
+
}
|
|
7455
|
+
|
|
7456
|
+
/**
|
|
7457
|
+
* Map one `/v1/models` row onto its serving transport.
|
|
7458
|
+
*
|
|
7459
|
+
* The wire's own `capabilities` list decides the transport: a row that serves
|
|
7460
|
+
* chat completions is a chat model, and a row whose only surface is
|
|
7461
|
+
* `/v1/images/generations` is routed to `openai-images` so
|
|
7462
|
+
* `generateImage`-style dispatch can reach it. Without that assignment the row
|
|
7463
|
+
* kept the discovery default (`openai-completions`) while still being marked
|
|
7464
|
+
* as an image model, so it was offered as an image target and then rejected by
|
|
7465
|
+
* every image client. The gateway bills image requests per request, never by
|
|
7466
|
+
* tokens, so those rows carry no token tariff.
|
|
7467
|
+
*/
|
|
7468
|
+
function mapSingularityApiModel(entry: OpenAICompatibleModelRecord, defaults: ModelSpec<Api>): ModelSpec<Api> {
|
|
7469
|
+
const capabilities = singularityApiCapabilities(entry);
|
|
7470
|
+
const capability = capabilities.find(candidate => candidate.endpoint === SINGULARITYAPI_CHAT_ENDPOINT);
|
|
7471
|
+
if (
|
|
7472
|
+
capability === undefined &&
|
|
7473
|
+
capabilities.some(candidate => candidate.endpoint === SINGULARITYAPI_IMAGE_ENDPOINT)
|
|
7474
|
+
) {
|
|
7475
|
+
return {
|
|
7476
|
+
...defaults,
|
|
7477
|
+
api: "openai-images",
|
|
7478
|
+
name: toModelName(entry.name, defaults.name),
|
|
7479
|
+
kind: "image",
|
|
7480
|
+
reasoning: false,
|
|
7481
|
+
input: ["text", "image"],
|
|
7482
|
+
supportsTools: false,
|
|
7483
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
7484
|
+
contextWindow: null,
|
|
7485
|
+
maxTokens: null,
|
|
7486
|
+
};
|
|
7487
|
+
}
|
|
7488
|
+
return {
|
|
7489
|
+
...defaults,
|
|
7490
|
+
name: toModelName(entry.name, defaults.name),
|
|
7491
|
+
contextWindow: toPositiveNumber(capability?.context_window_tokens, defaults.contextWindow),
|
|
7492
|
+
maxTokens: toPositiveNumber(capability?.maximum_output_tokens, defaults.maxTokens),
|
|
7493
|
+
cost: resolveSingularityApiCost(capability),
|
|
7494
|
+
};
|
|
7495
|
+
}
|
|
7496
|
+
/**
|
|
7497
|
+
* Core options shared by both SingularityAPI products. `mapModel` is supplied
|
|
7498
|
+
* only by the universal gateway, whose rows publish capability metadata; the
|
|
7499
|
+
* lane roster answers with bare ids and keeps the discovery defaults.
|
|
7500
|
+
*/
|
|
7501
|
+
function singularityApiModelManagerOptions(
|
|
7502
|
+
providerId: "singularityapi-dev" | "singularityapi-tech",
|
|
7503
|
+
canonical: string,
|
|
7504
|
+
config: SingularityApiModelManagerConfig | undefined,
|
|
7505
|
+
mapModel?: (entry: OpenAICompatibleModelRecord, defaults: ModelSpec<Api>) => ModelSpec<Api>,
|
|
7506
|
+
): ModelManagerOptions<Api> {
|
|
7507
|
+
const apiKey = config?.apiKey;
|
|
7508
|
+
const baseUrl = normalizeSingularityApiBaseUrl(config?.baseUrl, canonical);
|
|
7509
|
+
return {
|
|
7510
|
+
providerId,
|
|
7511
|
+
cacheProviderId: resolveModelCacheProviderId(providerId, { apiKey, baseUrl }),
|
|
7512
|
+
dynamicModelsAuthoritative: true,
|
|
7513
|
+
...(apiKey && {
|
|
7514
|
+
fetchDynamicModels: () =>
|
|
7515
|
+
fetchOpenAICompatibleModels<Api>({
|
|
7516
|
+
api: "openai-completions",
|
|
7517
|
+
provider: providerId,
|
|
7518
|
+
baseUrl,
|
|
7519
|
+
apiKey,
|
|
7520
|
+
...(mapModel && { mapModel }),
|
|
7521
|
+
fetch: config?.fetch,
|
|
7522
|
+
}),
|
|
7523
|
+
}),
|
|
7524
|
+
};
|
|
7525
|
+
}
|
|
7526
|
+
|
|
7527
|
+
/**
|
|
7528
|
+
* `singularityapi-dev` — SingularityAPI's pay-as-you-go universal gateway
|
|
7529
|
+
* (`api.singularityapi.dev`): chat completions over a 300+ model catalog,
|
|
7530
|
+
* plus image generation for the rows that advertise it.
|
|
7531
|
+
* `GET /v1/models` publishes each row's per-endpoint capabilities — context
|
|
7532
|
+
* window, max output tokens, and per-million pricing as 12-decimal strings —
|
|
7533
|
+
* with `cache-control: no-store`, so discovery reads limits and tariffs
|
|
7534
|
+
* straight off the wire and the endpoint list picks each row's transport.
|
|
7535
|
+
* Rows without a reasoning vocabulary stay non-reasoning; reviewed KDL rules
|
|
7536
|
+
* own the ladders the gateway leaves implicit (DeepSeek Flash/Pro, GPT-5.6
|
|
7537
|
+
* flagships), because a model discovered as non-reasoning never sends a
|
|
7538
|
+
* `reasoning_effort` and the gateway requires one alongside tools.
|
|
7539
|
+
*/
|
|
7540
|
+
export function singularityApiDevModelManagerOptions(
|
|
7541
|
+
config?: SingularityApiModelManagerConfig,
|
|
7542
|
+
): ModelManagerOptions<Api> {
|
|
7543
|
+
return singularityApiModelManagerOptions(
|
|
7544
|
+
"singularityapi-dev",
|
|
7545
|
+
SINGULARITYAPI_DEV_API_BASE_URL,
|
|
7546
|
+
config,
|
|
7547
|
+
mapSingularityApiModel,
|
|
7548
|
+
);
|
|
7549
|
+
}
|
|
7550
|
+
|
|
7551
|
+
/**
|
|
7552
|
+
* `singularityapi-tech` — SingularityAPI's slot-reserved DeepSeek lanes
|
|
7553
|
+
* (`api.singularityapi.tech`). `/v1/models` answers with bare `{id}` rows and
|
|
7554
|
+
* no capability metadata, so rows keep the discovery defaults and the
|
|
7555
|
+
* reviewed KDL rules own the wire shape, limits patch, and effort ladder.
|
|
7556
|
+
*/
|
|
7557
|
+
export function singularityApiTechModelManagerOptions(
|
|
7558
|
+
config?: SingularityApiModelManagerConfig,
|
|
7559
|
+
): ModelManagerOptions<Api> {
|
|
7560
|
+
return singularityApiModelManagerOptions("singularityapi-tech", SINGULARITYAPI_TECH_API_BASE_URL, config);
|
|
7561
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SingularityAPI gateways, shared so a host migration — or a self-hosted
|
|
3
|
+
* proxy override — touches a single module.
|
|
4
|
+
*
|
|
5
|
+
* SingularityAPI ships two unrelated products behind one brand: the
|
|
6
|
+
* pay-as-you-go universal gateway (`singularityapi-dev`) and the
|
|
7
|
+
* slot-reserved DeepSeek lanes (`singularityapi-tech`). They accept disjoint
|
|
8
|
+
* keys, so each provider resolves its own canonical endpoint from here.
|
|
9
|
+
*/
|
|
10
|
+
export const SINGULARITYAPI_DEV_API_BASE_URL = "https://api.singularityapi.dev/v1";
|
|
11
|
+
export const SINGULARITYAPI_TECH_API_BASE_URL = "https://api.singularityapi.tech/v1";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a configured SingularityAPI base URL onto its gateway's `/v1`
|
|
15
|
+
* surface.
|
|
16
|
+
*
|
|
17
|
+
* Every consumer must agree on this, because they key different things off the
|
|
18
|
+
* result: discovery and inference target it, and the model-cache namespace is
|
|
19
|
+
* hashed from it. `ModelRegistry` hashes the raw configured value while the
|
|
20
|
+
* model-manager options hash a `/v1`-suffixed one, so a disagreement would
|
|
21
|
+
* split the namespace discovery writes from the one the registry reads and the
|
|
22
|
+
* authoritative roster would never come back.
|
|
23
|
+
*
|
|
24
|
+
* `canonical` names the product the caller belongs to: a blank or
|
|
25
|
+
* whitespace-only value means "not configured" and resolves to that canonical
|
|
26
|
+
* host, while anything else keeps its host and gains the `/v1` segment if it
|
|
27
|
+
* omits one. Passing the wrong product's canonical URL would silently point a
|
|
28
|
+
* provider at the other gateway, so there is no default.
|
|
29
|
+
*/
|
|
30
|
+
export function normalizeSingularityApiBaseUrl(baseUrl: string | undefined, canonical: string): string {
|
|
31
|
+
const trimmed = baseUrl?.trim().replace(/\/+$/, "");
|
|
32
|
+
if (!trimmed) return canonical;
|
|
33
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
34
|
+
}
|