Skip to content

Commit 193c2f2

Browse files
authored
feat(api): add NanoGPT provider support (#1239)
* feat: add NanoGPT provider backend * feat: add NanoGPT provider settings * fix: harden NanoGPT provider boundaries * no-mistakes(review): Handle NanoGPT null metadata and reasoning capabilities * no-mistakes(review): Fix NanoGPT cache-capable request routing * fix(api): harden NanoGPT metadata handling * chore: retrigger checks --------- Co-authored-by: @taltas <6816042+taltas@users.noreply.github.com>
1 parent f7239c8 commit 193c2f2

60 files changed

Lines changed: 2039 additions & 13 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/cli/src/lib/utils/__tests__/context-window.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ describe("getContextWindow", () => {
1414
[providerIdentifiers.vercelAiGateway, "vercelAiGatewayModelId"],
1515
[providerIdentifiers.opencodeGo, "opencodeGoModelId"],
1616
[providerIdentifiers.kenari, "kenariModelId"],
17+
[providerIdentifiers.nanogpt, "nanoGptModelId"],
1718
[providerIdentifiers.zooGateway, "zooGatewayModelId"],
1819
] as const)("uses the provider-specific model field for %s", (provider, modelField) => {
1920
const config = { apiProvider: provider, [modelField]: "selected-model" } as ProviderSettings

apps/cli/src/lib/utils/context-window.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ function getModelIdForProvider(config: ProviderSettings): string | undefined {
5656
return config.opencodeGoModelId
5757
case providerIdentifiers.kenari:
5858
return config.kenariModelId
59+
case providerIdentifiers.nanogpt:
60+
return config.nanoGptModelId
5961
case providerIdentifiers.zooGateway:
6062
return config.zooGatewayModelId
6163
case providerIdentifiers.anthropic:
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
applyNanoGptRoutingPreference,
3+
dynamicProviders,
4+
getModelId,
5+
getProviderDefaultModelId,
6+
isSecretStateKey,
7+
nanoGptDefaultModelId,
8+
nanoGptDefaultRoutingPreference,
9+
providerIdentifiers,
10+
providerSettingsSchema,
11+
} from "../index.js"
12+
13+
describe("NanoGPT shared contract", () => {
14+
it("registers the stable dynamic-provider identity and default model", () => {
15+
expect(providerIdentifiers.nanogpt).toBe("nanogpt")
16+
expect(dynamicProviders).toContain("nanogpt")
17+
expect(getProviderDefaultModelId("nanogpt")).toBe(nanoGptDefaultModelId)
18+
})
19+
20+
it("classifies the API key as secret and resolves missing routing to auto", () => {
21+
expect(isSecretStateKey("nanoGptApiKey")).toBe(true)
22+
const settings = providerSettingsSchema.parse({ apiProvider: "nanogpt", nanoGptModelId: "model" })
23+
expect(settings.nanoGptRoutingPreference ?? nanoGptDefaultRoutingPreference).toBe("auto")
24+
expect(getModelId(settings)).toBe("model")
25+
})
26+
})
27+
28+
describe("applyNanoGptRoutingPreference", () => {
29+
it.each([
30+
["auto", "model"],
31+
["fast", "model:fast"],
32+
["cheap", "model:cheap"],
33+
["latency", "model:latency"],
34+
["throughput", "model:throughput"],
35+
["tools", "model:tools"],
36+
["caching", "model"],
37+
] as const)("maps %s routing", (preference, expected) => {
38+
expect(applyNanoGptRoutingPreference("model", preference)).toBe(expected)
39+
})
40+
41+
it.each([
42+
"speed",
43+
"fast",
44+
"throughput",
45+
"latency",
46+
"price",
47+
"cheap",
48+
"floor",
49+
"tools",
50+
"caching",
51+
"cache",
52+
"cached",
53+
])("replaces the recognized %s routing alias", (alias) => {
54+
expect(applyNanoGptRoutingPreference(`model:thinking:${alias}`, "cheap")).toBe("model:thinking:cheap")
55+
expect(applyNanoGptRoutingPreference(`model:thinking:${alias}`, "auto")).toBe("model:thinking")
56+
})
57+
58+
it("preserves legitimate identity suffixes", () => {
59+
expect(applyNanoGptRoutingPreference("model:thinking", "fast")).toBe("model:thinking:fast")
60+
expect(applyNanoGptRoutingPreference("model:thinking", "auto")).toBe("model:thinking")
61+
})
62+
63+
it("normalizes multiple trailing routing suffixes to exactly one active preference", () => {
64+
expect(applyNanoGptRoutingPreference("model:thinking:fast:cheap", "latency")).toBe("model:thinking:latency")
65+
expect(applyNanoGptRoutingPreference("model:thinking:FAST:CACHED", "auto")).toBe("model:thinking")
66+
})
67+
})

packages/types/src/__tests__/provider-identifiers.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const expectedProviderIdentifiers = [
3232
"deepseek",
3333
"opencode-go",
3434
"kenari",
35+
"nanogpt",
3536
"ollama",
3637
"lmstudio",
3738
"vscode-lm",
@@ -106,6 +107,7 @@ describe("provider identifiers", () => {
106107
providerIdentifiers.moonshot,
107108
providerIdentifiers.opencodeGo,
108109
providerIdentifiers.kenari,
110+
providerIdentifiers.nanogpt,
109111
providerIdentifiers.kimiCode,
110112
])
111113
expect(localProviders).toEqual([providerIdentifiers.ollama, providerIdentifiers.lmstudio])

packages/types/src/__tests__/provider-model-id.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const expectedModelIdKeys = [
1313
"vercelAiGatewayModelId",
1414
"opencodeGoModelId",
1515
"kenariModelId",
16+
"nanoGptModelId",
1617
"zooGatewayModelId",
1718
] as const
1819

packages/types/src/global-settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ export const SECRET_STATE_KEYS = [
324324
"vercelAiGatewayApiKey",
325325
"opencodeGoApiKey",
326326
"kenariApiKey",
327+
"nanoGptApiKey",
327328
"basetenApiKey",
328329
] as const
329330

packages/types/src/provider-identifiers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const providerIdentifiers = {
1414
deepseek: "deepseek",
1515
opencodeGo: "opencode-go",
1616
kenari: "kenari",
17+
nanogpt: "nanogpt",
1718
ollama: "ollama",
1819
lmstudio: "lmstudio",
1920
vscodeLm: "vscode-lm",

packages/types/src/provider-settings.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export {
66
OPEN_AI_CODEX_SERVICE_TIER_KEY,
77
kimiCodeAuthMethodSchema,
88
type KimiCodeAuthMethod,
9+
nanoGptDefaultRoutingPreference,
10+
nanoGptRoutingPreferences,
11+
nanoGptRoutingPreferenceSchema,
12+
type NanoGptRoutingPreference,
913
zaiApiLineSchema,
1014
type ZaiApiLine,
1115
} from "./provider-settings/index.js"
@@ -67,6 +71,7 @@ export const dynamicProviders = [
6771
providerIdentifiers.moonshot,
6872
providerIdentifiers.opencodeGo,
6973
providerIdentifiers.kenari,
74+
providerIdentifiers.nanogpt,
7075
providerIdentifiers.kimiCode,
7176
] as const
7277

@@ -280,6 +285,7 @@ export const modelIdKeys = [
280285
"vercelAiGatewayModelId",
281286
"opencodeGoModelId",
282287
"kenariModelId",
288+
"nanoGptModelId",
283289
"zooGatewayModelId",
284290
] as const satisfies readonly ModelIdKey[]
285291

@@ -508,6 +514,7 @@ export const MODELS_BY_PROVIDER: Record<
508514
},
509515
[providerIdentifiers.opencodeGo]: { id: providerIdentifiers.opencodeGo, label: "Opencode Go", models: [] },
510516
[providerIdentifiers.kenari]: { id: providerIdentifiers.kenari, label: "Kenari", models: [] },
517+
[providerIdentifiers.nanogpt]: { id: providerIdentifiers.nanogpt, label: "NanoGPT", models: [] },
511518
[providerIdentifiers.zooGateway]: { id: providerIdentifiers.zooGateway, label: "Zoo Gateway", models: [] },
512519

513520
// Local providers; models discovered from localhost endpoints.

packages/types/src/provider-settings/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { qwenCodeProviderDefinition } from "./qwen-code.js"
3030
import { vercelAiGatewayProviderDefinition } from "./vercel-ai-gateway.js"
3131
import { opencodeGoProviderDefinition } from "./opencode-go.js"
3232
import { kenariProviderDefinition } from "./kenari.js"
33+
import { nanoGptProviderDefinition } from "./nanogpt.js"
3334
import { zooGatewayProviderDefinition } from "./zoo-gateway.js"
3435
import { basetenProviderDefinition } from "./baseten.js"
3536

@@ -38,6 +39,12 @@ import type { ProviderDefinition } from "./common.js"
3839
export { OPEN_AI_CODEX_SERVICE_TIER_KEY } from "./openai-codex.js"
3940
export { kimiCodeAuthMethodSchema, type KimiCodeAuthMethod } from "./kimi-code.js"
4041
export { zaiApiLineSchema, type ZaiApiLine } from "./zai.js"
42+
export {
43+
nanoGptDefaultRoutingPreference,
44+
nanoGptRoutingPreferences,
45+
nanoGptRoutingPreferenceSchema,
46+
type NanoGptRoutingPreference,
47+
} from "./nanogpt.js"
4148
export type { ProviderDefinition } from "./common.js"
4249

4350
export const providerDefinitionList = [
@@ -73,6 +80,7 @@ export const providerDefinitionList = [
7380
vercelAiGatewayProviderDefinition,
7481
opencodeGoProviderDefinition,
7582
kenariProviderDefinition,
83+
nanoGptProviderDefinition,
7684
zooGatewayProviderDefinition,
7785
basetenProviderDefinition,
7886
] as const satisfies readonly ProviderDefinition[]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { z } from "zod"
2+
3+
import { providerIdentifiers } from "../provider-identifiers.js"
4+
import { baseProviderSettingsShape, createModelIdAccessor, createProviderDefinition } from "./common.js"
5+
6+
const NANOGPT_MODEL_ID_FIELD = "nanoGptModelId"
7+
8+
export const nanoGptRoutingPreferences = ["auto", "fast", "cheap", "latency", "throughput", "tools", "caching"] as const
9+
10+
export const nanoGptRoutingPreferenceSchema = z.enum(nanoGptRoutingPreferences)
11+
export type NanoGptRoutingPreference = z.infer<typeof nanoGptRoutingPreferenceSchema>
12+
export const nanoGptDefaultRoutingPreference: NanoGptRoutingPreference = "auto"
13+
14+
export const nanoGptProviderDefinition = createProviderDefinition({
15+
apiProvider: providerIdentifiers.nanogpt,
16+
modelIdKey: NANOGPT_MODEL_ID_FIELD,
17+
getModelId: createModelIdAccessor(NANOGPT_MODEL_ID_FIELD),
18+
schema: {
19+
...baseProviderSettingsShape,
20+
nanoGptApiKey: z.string().optional(),
21+
[NANOGPT_MODEL_ID_FIELD]: z.string().optional(),
22+
nanoGptRoutingPreference: nanoGptRoutingPreferenceSchema.optional(),
23+
},
24+
})

0 commit comments

Comments
 (0)