fix(ai): validated together login against models endpoint
Together `/login` key validation chat-completed against the hardcoded model `moonshotai/Kimi-K2.5`, which is not on Together's serverless catalog. Together rejects it with HTTP 400 `model_not_available`, so no valid key could ever pass validation. Switch Together to the model-agnostic `models-endpoint` validation against `https://api.together.xyz/v1/models`, matching the pattern used by the other API-key providers. Fixes #8328
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `/login together` always failing with HTTP 400 `model_not_available`: key validation chat-completed against the hardcoded non-serverless model `moonshotai/Kimi-K2.5`, so no valid key could pass. Validation now probes Together's authenticated `/v1/models` listing, matching the model-agnostic approach used by other API-key providers ([#8328](https://github.com/can1357/oh-my-pi/issues/8328)).
|
||||
|
||||
## [17.2.15] - 2026-08-12
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -8,10 +8,14 @@ export const loginTogether = createApiKeyLogin({
|
||||
promptMessage: "Paste your Together API key",
|
||||
placeholder: "sk-...",
|
||||
validation: {
|
||||
kind: "chat-completions",
|
||||
// Validate against the authenticated models listing, not a chat
|
||||
// completion: Together rejects models that only exist behind a dedicated
|
||||
// endpoint (e.g. `moonshotai/Kimi-K2.5`) with an HTTP 400
|
||||
// `model_not_available`, which failed key validation for every valid key
|
||||
// (issue #8328). The `/v1/models` listing is model-agnostic.
|
||||
kind: "models-endpoint",
|
||||
provider: "together",
|
||||
baseUrl: "https://api.together.xyz/v1",
|
||||
model: "moonshotai/Kimi-K2.5",
|
||||
modelsUrl: "https://api.together.xyz/v1/models",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, expect, test, vi } from "bun:test";
|
||||
import { loginTogether } from "../src/registry/together";
|
||||
import type { FetchImpl } from "../src/types";
|
||||
|
||||
// Together's serverless API rejects models that only exist behind a dedicated
|
||||
// endpoint (e.g. `moonshotai/Kimi-K2.5`) with an HTTP 400 `model_not_available`
|
||||
// error — even when the pasted key is perfectly valid. Login validation must
|
||||
// therefore not depend on chat-completing against a specific model; it must
|
||||
// probe an authenticated, model-agnostic endpoint. Regression guard for #8328.
|
||||
const NON_SERVERLESS_400 = {
|
||||
id: "ovsZQhk-2kFHot",
|
||||
error: {
|
||||
message:
|
||||
"Unable to access non-serverless model moonshotai/Kimi-K2.5. Please visit https://api.together.ai/models/moonshotai/Kimi-K2.5 to create and start a new dedicated endpoint for the model.",
|
||||
type: "invalid_request_error",
|
||||
param: null,
|
||||
code: "model_not_available",
|
||||
},
|
||||
};
|
||||
|
||||
describe("Together login (#8328)", () => {
|
||||
test("validates a valid key against the models endpoint, not a hardcoded model", async () => {
|
||||
const requests: Array<{ url: string; method: string | undefined }> = [];
|
||||
const fetchMock: FetchImpl = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
||||
const url = String(input);
|
||||
requests.push({ url, method: init?.method });
|
||||
// Simulate Together: chat-completions with a non-serverless model 400s,
|
||||
// while the authenticated models listing succeeds for a valid key.
|
||||
if (url.endsWith("/chat/completions")) {
|
||||
return Response.json(NON_SERVERLESS_400, { status: 400 });
|
||||
}
|
||||
if (url.endsWith("/models")) {
|
||||
return Response.json({ object: "list", data: [] });
|
||||
}
|
||||
return Response.json({ error: "unexpected" }, { status: 500 });
|
||||
});
|
||||
|
||||
const apiKey = await loginTogether({
|
||||
onPrompt: async () => " together-valid-key ",
|
||||
fetch: fetchMock,
|
||||
});
|
||||
|
||||
expect(apiKey).toBe("together-valid-key");
|
||||
// The only validation request must be the authenticated models listing.
|
||||
expect(requests).toEqual([{ url: "https://api.together.xyz/v1/models", method: "GET" }]);
|
||||
});
|
||||
|
||||
test("still rejects an invalid key", async () => {
|
||||
const fetchMock: FetchImpl = vi.fn(async () =>
|
||||
Response.json({ error: { message: "Invalid API key provided" } }, { status: 401 }),
|
||||
);
|
||||
|
||||
await expect(
|
||||
loginTogether({
|
||||
onPrompt: async () => "bad-key",
|
||||
fetch: fetchMock,
|
||||
}),
|
||||
).rejects.toThrow("together API key validation failed (401)");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user