@oh-my-pi/pi-catalog 17.2.2 → 17.2.3

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.
@@ -9,6 +9,7 @@ import type { ModelManagerConfig, ProviderCatalogEntry, ProviderDescriptor } fro
9
9
  import { googleModelManagerOptions, googleVertexModelManagerOptions } from "./google";
10
10
  import { ollamaCloudModelManagerOptions } from "./ollama";
11
11
  import {
12
+ aiandModelManagerOptions,
12
13
  aimlApiModelManagerOptions,
13
14
  alibabaCodingPlanModelManagerOptions,
14
15
  alibabaTokenPlanModelManagerOptions,
@@ -65,6 +66,14 @@ import {
65
66
  } from "./special";
66
67
 
67
68
  export const CATALOG_PROVIDERS = [
69
+ {
70
+ id: "aiand",
71
+ defaultModel: "moonshotai/kimi-k2.7-code",
72
+ envVars: ["AIAND_API_KEY"],
73
+ createModelManagerOptions: (config: ModelManagerConfig) => aiandModelManagerOptions(config),
74
+ dynamicModelsAuthoritative: true,
75
+ catalogDiscovery: { label: "ai&" },
76
+ },
68
77
  {
69
78
  id: "aimlapi",
70
79
  defaultModel: "gpt-5.5-2026-04-23",
@@ -3796,6 +3796,175 @@ export function sakanaModelManagerOptions(config?: SakanaModelManagerConfig): Mo
3796
3796
  };
3797
3797
  }
3798
3798
 
3799
+ // ---------------------------------------------------------------------------
3800
+ // 16.6 ai& (aiand.com)
3801
+ // ---------------------------------------------------------------------------
3802
+
3803
+ const AIAND_DEFAULT_BASE_URL = "https://api.aiand.com/v1";
3804
+
3805
+ /** `reasoning_efforts` wire values ai& reports, mapped onto pi effort levels. */
3806
+ const AIAND_EFFORT_BY_WIRE_VALUE: Record<string, Effort> = {
3807
+ minimal: Effort.Minimal,
3808
+ low: Effort.Low,
3809
+ medium: Effort.Medium,
3810
+ high: Effort.High,
3811
+ xhigh: Effort.XHigh,
3812
+ max: Effort.Max,
3813
+ };
3814
+
3815
+ function normalizeAiandBaseUrl(baseUrl: string | undefined): string {
3816
+ const value = baseUrl?.trim() || AIAND_DEFAULT_BASE_URL;
3817
+ const normalized = value.replace(/\/+$/, "");
3818
+ return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
3819
+ }
3820
+
3821
+ function createAiandStaticModel(
3822
+ id: string,
3823
+ name: string,
3824
+ cost: { input: number; output: number },
3825
+ contextWindow: number,
3826
+ input: ModelSpec<"openai-completions">["input"],
3827
+ ): ModelSpec<"openai-completions"> {
3828
+ return {
3829
+ id,
3830
+ name,
3831
+ api: "openai-completions",
3832
+ provider: "aiand",
3833
+ baseUrl: AIAND_DEFAULT_BASE_URL,
3834
+ reasoning: true,
3835
+ input: [...input],
3836
+ cost: { input: cost.input, output: cost.output, cacheRead: 0, cacheWrite: 0 },
3837
+ contextWindow,
3838
+ maxTokens: null,
3839
+ thinking: { mode: "effort", efforts: [Effort.Low, Effort.Medium, Effort.High], defaultLevel: Effort.Medium },
3840
+ };
3841
+ }
3842
+
3843
+ /**
3844
+ * Documented ai& catalog (docs.aiand.com/models/catalog, 2026-08) bundled so
3845
+ * the provider is usable when generation and first boot have no live key.
3846
+ * The org-scoped `/v1/models` response is authoritative once discovery runs.
3847
+ */
3848
+ export const AIAND_STATIC_MODELS: readonly ModelSpec<"openai-completions">[] = [
3849
+ createAiandStaticModel("qwen/qwen3.6-27b", "Qwen3.6 27B", { input: 0, output: 0 }, 262_144, ["text"]),
3850
+ createAiandStaticModel(
3851
+ "deepseek-ai/deepseek-v4-flash",
3852
+ "DeepSeek V4 Flash",
3853
+ { input: 0.15, output: 0.25 },
3854
+ 1_000_000,
3855
+ ["text"],
3856
+ ),
3857
+ createAiandStaticModel("google/gemma-4-31b-it", "Gemma 4 31B IT", { input: 0.2, output: 0.5 }, 262_144, [
3858
+ "text",
3859
+ "image",
3860
+ ]),
3861
+ createAiandStaticModel("openai/gpt-oss-120b", "GPT OSS 120B", { input: 0.15, output: 0.6 }, 131_072, ["text"]),
3862
+ createAiandStaticModel("deepseek-ai/deepseek-v4-pro", "DeepSeek V4 Pro", { input: 1, output: 2.5 }, 1_000_000, [
3863
+ "text",
3864
+ ]),
3865
+ createAiandStaticModel("moonshotai/kimi-k2.7-code", "Kimi K2.7 Code", { input: 0.75, output: 3.5 }, 262_144, [
3866
+ "text",
3867
+ "image",
3868
+ ]),
3869
+ createAiandStaticModel("moonshotai/kimi-k2.6", "Kimi K2.6", { input: 0.85, output: 3.5 }, 262_144, [
3870
+ "text",
3871
+ "image",
3872
+ ]),
3873
+ createAiandStaticModel("zai-org/glm-5.2", "GLM 5.2", { input: 1, output: 4 }, 1_000_000, ["text"]),
3874
+ createAiandStaticModel("zai-org/glm-5.1", "GLM 5.1", { input: 1.4, output: 4.4 }, 202_752, ["text"]),
3875
+ ];
3876
+
3877
+ const AIAND_STATIC_MODEL_IDS = AIAND_STATIC_MODELS.map(model => model.id);
3878
+
3879
+ function mapAiandThinking(entry: OpenAICompatibleModelRecord): ThinkingConfig | undefined {
3880
+ const efforts = Array.isArray(entry.reasoning_efforts)
3881
+ ? entry.reasoning_efforts.flatMap(value =>
3882
+ typeof value === "string" && AIAND_EFFORT_BY_WIRE_VALUE[value] ? [AIAND_EFFORT_BY_WIRE_VALUE[value]] : [],
3883
+ )
3884
+ : [];
3885
+ if (efforts.length === 0) {
3886
+ return undefined;
3887
+ }
3888
+ const defaultLevel =
3889
+ typeof entry.reasoning_effort_default === "string"
3890
+ ? AIAND_EFFORT_BY_WIRE_VALUE[entry.reasoning_effort_default]
3891
+ : undefined;
3892
+ return {
3893
+ mode: "effort",
3894
+ efforts,
3895
+ ...(defaultLevel && efforts.includes(defaultLevel) && { defaultLevel }),
3896
+ };
3897
+ }
3898
+
3899
+ /**
3900
+ * ai& reports prices as decimal strings per 1M tokens in the org's billing
3901
+ * currency (`usd` or `jpy`). Costs are only mapped for USD orgs — JPY figures
3902
+ * would corrupt the USD-denominated cost model, so they fall back to zero.
3903
+ */
3904
+ function mapAiandCost(entry: OpenAICompatibleModelRecord): ModelSpec<"openai-completions">["cost"] {
3905
+ if (typeof entry.currency === "string" && entry.currency !== "usd") {
3906
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
3907
+ }
3908
+ return {
3909
+ input: toPositiveNumber(entry.input_per_1m, 0),
3910
+ output: toPositiveNumber(entry.output_per_1m, 0),
3911
+ cacheRead: 0,
3912
+ cacheWrite: 0,
3913
+ };
3914
+ }
3915
+
3916
+ function mapAiandModel(
3917
+ entry: OpenAICompatibleModelRecord,
3918
+ defaults: ModelSpec<"openai-completions">,
3919
+ ): ModelSpec<"openai-completions"> {
3920
+ const capabilities: unknown[] = Array.isArray(entry.capabilities) ? entry.capabilities : [];
3921
+ const reasoning = capabilities.includes("reasoning");
3922
+ const thinking = reasoning ? mapAiandThinking(entry) : undefined;
3923
+ const description =
3924
+ typeof entry.description === "string" && entry.description.trim() ? entry.description : undefined;
3925
+ return {
3926
+ ...defaults,
3927
+ name: description ?? toModelName(entry.name, defaults.name),
3928
+ reasoning,
3929
+ input: capabilities.includes("vision") ? ["text", "image"] : ["text"],
3930
+ cost: mapAiandCost(entry),
3931
+ contextWindow: toPositiveNumber(entry.context_window, null),
3932
+ ...(thinking && { thinking }),
3933
+ };
3934
+ }
3935
+
3936
+ export interface AiandModelManagerConfig {
3937
+ apiKey?: string;
3938
+ baseUrl?: string;
3939
+ fetch?: FetchImpl;
3940
+ }
3941
+
3942
+ /**
3943
+ * ai& (aiand.com) model manager: OpenAI-compatible chat completions with an
3944
+ * org-scoped `/v1/models` catalog carrying context, capability, effort, and
3945
+ * pricing metadata, so discovery is authoritative over the bundled seed.
3946
+ */
3947
+ export function aiandModelManagerOptions(config?: AiandModelManagerConfig): ModelManagerOptions<"openai-completions"> {
3948
+ const apiKey = config?.apiKey;
3949
+ const baseUrl = normalizeAiandBaseUrl(config?.baseUrl ?? Bun.env.AIAND_BASE_URL);
3950
+ return {
3951
+ providerId: "aiand",
3952
+ dynamicModelsAuthoritative: true,
3953
+ dropCachedModelIdsOnStaticMismatch: AIAND_STATIC_MODEL_IDS,
3954
+ ...(apiKey && {
3955
+ fetchDynamicModels: () =>
3956
+ fetchOpenAICompatibleModels({
3957
+ api: "openai-completions",
3958
+ provider: "aiand",
3959
+ baseUrl,
3960
+ apiKey,
3961
+ mapModel: (entry, defaults) => mapAiandModel(entry, defaults),
3962
+ fetch: config?.fetch,
3963
+ }),
3964
+ }),
3965
+ };
3966
+ }
3967
+
3799
3968
  // ---------------------------------------------------------------------------
3800
3969
  // 17. Qwen Portal
3801
3970
  // ---------------------------------------------------------------------------