Skip to content

Commit c2659ec

Browse files
Share cloud task contracts and model policy
Generated-By: PostHog Code Task-Id: 40c57a59-b4e1-4760-8e56-ecd03e9c2f0f
1 parent 0717517 commit c2659ec

17 files changed

Lines changed: 1024 additions & 736 deletions
Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,6 @@
1-
import type { Adapter } from "@posthog/shared";
2-
import { getEffortOptions as getClaudeEffortOptions } from "./claude/session/models";
3-
import { getReasoningEffortOptions as getCodexReasoningEffortOptions } from "./codex-app-server/models";
4-
5-
export type SupportedReasoningEffort =
6-
| "low"
7-
| "medium"
8-
| "high"
9-
| "xhigh"
10-
| "max";
11-
12-
export interface ReasoningEffortOption {
13-
value: SupportedReasoningEffort;
14-
name: string;
15-
}
16-
17-
export function getReasoningEffortOptions(
18-
adapter: Adapter,
19-
modelId: string,
20-
): ReasoningEffortOption[] | null {
21-
const options =
22-
adapter === "codex"
23-
? getCodexReasoningEffortOptions(modelId)
24-
: getClaudeEffortOptions(modelId);
25-
26-
return options as ReasoningEffortOption[] | null;
27-
}
28-
29-
export function isSupportedReasoningEffort(
30-
adapter: Adapter,
31-
modelId: string,
32-
value: string,
33-
): value is SupportedReasoningEffort {
34-
return (
35-
getReasoningEffortOptions(adapter, modelId)?.some(
36-
(option) => option.value === value,
37-
) ?? false
38-
);
39-
}
1+
export {
2+
getReasoningEffortOptions,
3+
isSupportedReasoningEffort,
4+
type ReasoningEffortOption,
5+
type SupportedReasoningEffort,
6+
} from "@posthog/shared";
Lines changed: 1 addition & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,5 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
2-
import {
3-
compareModelsForPicker,
4-
fetchGatewayModels,
5-
fetchModelsList,
6-
formatGatewayModelName,
7-
type GatewayModel,
8-
getClaudeModelRecency,
9-
isAnthropicModel,
10-
isBlockedModelId,
11-
isCloudflareModel,
12-
pickAllowedModel,
13-
} from "./gateway-models";
14-
15-
const model = (id: string, owned_by = ""): GatewayModel => ({
16-
id,
17-
owned_by,
18-
context_window: 128000,
19-
supports_streaming: true,
20-
supports_vision: false,
21-
allowed: true,
22-
});
23-
24-
describe("formatGatewayModelName", () => {
25-
it("keeps Claude models in friendly title case", () => {
26-
expect(
27-
formatGatewayModelName({
28-
id: "claude-opus-4-8",
29-
owned_by: "anthropic",
30-
context_window: 200000,
31-
supports_streaming: true,
32-
supports_vision: true,
33-
allowed: true,
34-
}),
35-
).toBe("Claude Opus 4.8");
36-
});
37-
38-
it("uppercases the GPT acronym in OpenAI model ids", () => {
39-
expect(
40-
formatGatewayModelName({
41-
id: "GPT-5.5",
42-
owned_by: "openai",
43-
context_window: 200000,
44-
supports_streaming: true,
45-
supports_vision: true,
46-
allowed: true,
47-
}),
48-
).toBe("GPT-5.5");
49-
});
50-
51-
it("strips the openai/ prefix, uppercases GPT, and title-cases the suffix", () => {
52-
expect(
53-
formatGatewayModelName({
54-
id: "openai/gpt-5.6-sol",
55-
owned_by: "openai",
56-
context_window: 200000,
57-
supports_streaming: true,
58-
supports_vision: true,
59-
allowed: true,
60-
}),
61-
).toBe("GPT-5.6 Sol");
62-
});
63-
64-
it("formats Cloudflare models as the final path segment with GLM uppercased", () => {
65-
expect(
66-
formatGatewayModelName({
67-
id: "@cf/zai-org/glm-5.2",
68-
owned_by: "cloudflare",
69-
context_window: 128000,
70-
supports_streaming: true,
71-
supports_vision: false,
72-
allowed: true,
73-
}),
74-
).toBe("GLM-5.2");
75-
});
76-
77-
it("leaves non-acronym Cloudflare models lowercase", () => {
78-
expect(
79-
formatGatewayModelName({
80-
id: "@cf/meta/llama-3.1-8b-instruct",
81-
owned_by: "cloudflare",
82-
context_window: 128000,
83-
supports_streaming: true,
84-
supports_vision: false,
85-
allowed: true,
86-
}),
87-
).toBe("llama-3.1-8b-instruct");
88-
});
89-
90-
it("blocks deprecated Claude gateway models", () => {
91-
expect(isBlockedModelId("claude-opus-4-5")).toBe(true);
92-
expect(isBlockedModelId("claude-opus-4-6")).toBe(true);
93-
expect(isBlockedModelId("claude-sonnet-4-5")).toBe(true);
94-
expect(isBlockedModelId("claude-haiku-4-5")).toBe(true);
95-
expect(isBlockedModelId("ANTHROPIC/CLAUDE-HAIKU-4-5")).toBe(true);
96-
});
97-
98-
it("blocks deprecated Codex gateway models", () => {
99-
expect(isBlockedModelId("gpt-5.2")).toBe(true);
100-
expect(isBlockedModelId("gpt-5.3")).toBe(true);
101-
expect(isBlockedModelId("gpt-5.3-codex")).toBe(true);
102-
expect(isBlockedModelId("openai/gpt-5.2")).toBe(true);
103-
expect(isBlockedModelId("OPENAI/GPT-5.3")).toBe(true);
104-
expect(isBlockedModelId("OPENAI/GPT-5.3-CODEX")).toBe(true);
105-
});
106-
});
107-
108-
describe("getClaudeModelRecency", () => {
109-
it.each([
110-
["claude-haiku-4-5", 4005],
111-
["claude-sonnet-4-6", 4006],
112-
["claude-opus-4-7", 4007],
113-
["claude-opus-4-8", 4008],
114-
["claude-sonnet-5", 5000],
115-
["claude-fable-5", 5000],
116-
])("ranks %s by its embedded version (%i)", (modelId, rank) => {
117-
expect(getClaudeModelRecency(modelId)).toBe(rank);
118-
});
119-
120-
it("ignores a trailing date suffix when reading the version", () => {
121-
expect(getClaudeModelRecency("claude-haiku-4-5-20251001")).toBe(4005);
122-
});
123-
124-
it("ranks a model with no recognisable version as newest", () => {
125-
expect(getClaudeModelRecency("claude-mystery")).toBe(
126-
Number.MAX_SAFE_INTEGER,
127-
);
128-
expect(getClaudeModelRecency("claude-mystery")).toBeGreaterThan(
129-
getClaudeModelRecency("claude-fable-5"),
130-
);
131-
});
132-
});
133-
134-
describe("compareModelsForPicker", () => {
135-
it("groups models by family, most capable first, newest version first", () => {
136-
// Models as the gateway might return them — arbitrary order.
137-
const gatewayOrder = [
138-
"claude-fable-5",
139-
"claude-opus-4-7",
140-
"claude-mystery",
141-
"claude-sonnet-5",
142-
"claude-haiku-4-5",
143-
"claude-sonnet-4-6",
144-
"claude-opus-4-8",
145-
];
146-
const displayed = [...gatewayOrder].sort(compareModelsForPicker);
147-
expect(displayed).toEqual([
148-
"claude-fable-5",
149-
"claude-opus-4-8",
150-
"claude-opus-4-7",
151-
"claude-sonnet-5",
152-
"claude-sonnet-4-6",
153-
"claude-haiku-4-5",
154-
"claude-mystery",
155-
]);
156-
});
157-
});
2+
import { fetchGatewayModels, fetchModelsList } from "./gateway-models";
1583

1594
describe("gateway model fetch timeout", () => {
1605
afterEach(() => {
@@ -266,69 +111,3 @@ describe("gateway models cache", () => {
266111
expect(models[0]?.context_window).toBe(1_000_000);
267112
});
268113
});
269-
270-
describe("isCloudflareModel", () => {
271-
it.each([
272-
{ id: "@cf/zai-org/glm-5.2", owned_by: "cloudflare", expected: true },
273-
{ id: "claude-opus-4-8", owned_by: "anthropic", expected: false },
274-
{ id: "@cf/zai-org/glm-5.2", owned_by: "", expected: true },
275-
{ id: "gpt-5.5", owned_by: "", expected: false },
276-
// A Cloudflare-served model can report an upstream owner; the `@cf/` prefix still wins.
277-
{ id: "@cf/openai/gpt-oss", owned_by: "openai", expected: true },
278-
])(
279-
"isCloudflareModel($id, owned_by=$owned_by) → $expected",
280-
({ id, owned_by, expected }) => {
281-
expect(isCloudflareModel(model(id, owned_by))).toBe(expected);
282-
},
283-
);
284-
285-
it("does not classify Cloudflare models as Anthropic", () => {
286-
// The Claude adapter accepts both, but they must stay distinguishable.
287-
const glm = model("@cf/zai-org/glm-5.2", "cloudflare");
288-
expect(isCloudflareModel(glm)).toBe(true);
289-
expect(isAnthropicModel(glm)).toBe(false);
290-
});
291-
});
292-
293-
describe("pickAllowedModel", () => {
294-
const entry = (id: string, allowed: boolean) => ({ id, allowed });
295-
296-
it.each([
297-
[
298-
"keeps an allowed preferred model",
299-
[entry("claude-opus-4-8", true)],
300-
"claude-opus-4-8",
301-
"claude-opus-4-8",
302-
],
303-
[
304-
"keeps a preferred model absent from the list",
305-
[entry("claude-opus-4-8", true)],
306-
"claude-sonnet-5",
307-
"claude-sonnet-5",
308-
],
309-
[
310-
"moves a restricted preferred model to the newest allowed one",
311-
[
312-
entry("claude-opus-4-8", false),
313-
entry("claude-sonnet-4-6", true),
314-
entry("@cf/zai-org/glm-5.2", true),
315-
],
316-
"claude-opus-4-8",
317-
"@cf/zai-org/glm-5.2",
318-
],
319-
[
320-
"keeps the preferred model when everything is restricted",
321-
[entry("claude-opus-4-8", false)],
322-
"claude-opus-4-8",
323-
"claude-opus-4-8",
324-
],
325-
[
326-
"keeps the preferred model when the list is empty",
327-
[],
328-
"claude-opus-4-8",
329-
"claude-opus-4-8",
330-
],
331-
] as const)("%s", (_name, models, preferred, expected) => {
332-
expect(pickAllowedModel(models, preferred)).toBe(expected);
333-
});
334-
});

0 commit comments

Comments
 (0)