feat(providers): add 1min.ai provider (#11631)

Merged via /merge-batch (lote 2026-08-26 batch 2, v3.8.51). Boarded no worktree combinado junto com outras ~20 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e 292 testes focados (unit) + 18 (vitest) passando. Obrigado pela contribuição.
This commit is contained in:
zero-executioner
2026-08-26 08:21:48 -04:00
committed by GitHub
parent 9f1f5ecf1f
commit 8abcd639d1
11 changed files with 611 additions and 2 deletions

View File

@@ -63,6 +63,7 @@ import { nubeProvider } from "./registry/nube/index.ts";
import { clinepassProvider } from "./registry/clinepass/index.ts";
import { sparkdeskProvider } from "./registry/sparkdesk/index.ts";
import { nlpcloudProvider } from "./registry/nlpcloud/index.ts";
import { oneminaiProvider } from "./registry/oneminai/index.ts";
import { nvidiaProvider } from "./registry/nvidia/index.ts";
import { api_airforceProvider } from "./registry/api-airforce/index.ts";
import { mistralProvider } from "./registry/mistral/index.ts";
@@ -332,6 +333,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
clinepass: clinepassProvider,
sparkdesk: sparkdeskProvider,
nlpcloud: nlpcloudProvider,
oneminai: oneminaiProvider,
nvidia: nvidiaProvider,
"api-airforce": api_airforceProvider,
mistral: mistralProvider,

View File

@@ -0,0 +1,30 @@
import type { RegistryEntry } from "../../shared.ts";
// 1min.ai (docs.1min.ai) — a chat aggregator exposing many upstream models
// through one custom API. Not OpenAI-compatible at the wire level (single
// `promptObject.prompt` string instead of a `messages` array, real SSE with
// event:/data: framing instead of raw text deltas, "API-KEY" auth header
// instead of Authorization: Bearer) — see open-sse/executors/oneminai.ts for
// the request/response translation. `format: "openai"` here describes the
// client-facing surface OmniRoute exposes, not 1min.ai's actual wire format.
export const oneminaiProvider: RegistryEntry = {
id: "oneminai",
alias: "1min",
format: "openai",
executor: "default",
baseUrl: "https://api.1min.ai/api/chat-with-ai",
authType: "apikey",
authHeader: "api-key",
// The model catalog is loaded dynamically per-account/plan on 1min.ai's own
// dashboard rather than published as a stable public list, so only the
// model shown in every one of 1min.ai's own docs examples is statically
// catalogued; passthroughModels lets any other slug the account has access
// to be used by id.
passthroughModels: true,
liveCatalogAuthoritative: false,
// No tool/function-calling, JSON mode, or vision support is wired up by the
// executor's translation (1min.ai's attachments.images/files feature would
// need separate Asset API upload plumbing this provider doesn't implement).
unsupportedParams: ["tools", "tool_choice", "functions", "function_call", "response_format"],
models: [{ id: "gpt-4o-mini", name: "GPT-4o Mini" }],
};

View File

@@ -60,6 +60,8 @@ const lazyExecutors: Record<string, () => Promise<BaseExecutor>> = {
gitlab: () => import("./gitlab.ts").then((m) => new m.GitlabExecutor()),
"gitlab-duo": () => import("./gitlab.ts").then((m) => new m.GitlabExecutor("gitlab-duo")),
nlpcloud: () => import("./nlpcloud.ts").then((m) => new m.NlpCloudExecutor()),
oneminai: () => import("./oneminai.ts").then((m) => new m.OneMinAiExecutor()),
"1min": () => import("./oneminai.ts").then((m) => new m.OneMinAiExecutor()), // Alias
pollinations: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()),
pol: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()), // Alias
"cloudflare-ai": () => import("./cloudflare-ai.ts").then((m) => new m.CloudflareAIExecutor()),

View File

@@ -0,0 +1,313 @@
import { randomUUID } from "node:crypto";
import {
BaseExecutor,
mergeUpstreamExtraHeaders,
type ExecuteInput,
type ProviderCredentials,
} from "./base.ts";
import { PROVIDERS } from "../config/constants.ts";
import { buildErrorBody } from "../utils/error.ts";
type JsonRecord = Record<string, unknown>;
type OpenAIMessage = {
role?: string;
content?: unknown;
};
const CHAT_URL = "https://api.1min.ai/api/chat-with-ai";
const ROLE_LABELS: Record<string, string> = {
system: "System",
developer: "System",
user: "User",
assistant: "Assistant",
};
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
}
function extractTextContent(content: unknown): string {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
return content
.map((part) => {
if (!part || typeof part !== "object") return "";
const item = part as Record<string, unknown>;
return item.type === "text" && typeof item.text === "string" ? item.text : "";
})
.filter((text) => text.length > 0)
.join("\n");
}
/**
* 1min.ai's Chat with AI API takes one `promptObject.prompt` string, not an
* OpenAI `messages` array — multi-turn context is normally carried server-side
* via `promptObject.conversationId` (see docs.1min.ai/docs/api/chat-with-ai-api),
* which requires a prior POST /api/conversations call and a stable conversation
* identity that stateless OpenAI-compatible clients don't provide. Rather than
* half-implement that, a single user message passes through unchanged and
* multi-turn history is flattened into a labeled transcript.
*/
export function buildPrompt(messages: OpenAIMessage[] | undefined): string {
const list = Array.isArray(messages) ? messages : [];
if (list.length === 1 && list[0]?.role === "user") {
return extractTextContent(list[0].content);
}
return list
.map((message) => {
const role = String(message?.role || "user").toLowerCase();
const text = extractTextContent(message?.content);
const label = ROLE_LABELS[role] || role;
return `${label}: ${text}`;
})
.filter((line) => line.length > 0)
.join("\n\n");
}
function buildSseChunk(data: unknown): string {
return `data: ${JSON.stringify(data)}\n\n`;
}
function buildOpenAiJsonCompletion(content: string, model: string, id: string, created: number): Response {
return new Response(
JSON.stringify({
id,
object: "chat.completion",
created,
model,
choices: [{ index: 0, message: { role: "assistant", content }, finish_reason: "stop" }],
// 1min.ai's response shape carries no token-usage fields.
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
function toOpenAiErrorResponse(status: number, message: string, upstreamDetails?: unknown): Response {
return new Response(JSON.stringify(buildErrorBody(status, message, upstreamDetails)), {
status,
headers: { "Content-Type": "application/json" },
});
}
/**
* Parse 1min.ai's real Server-Sent Events (event: content|result|done|error,
* data: {...}) from the upstream Response body and re-emit them as standard
* OpenAI chat.completion.chunk SSE.
*/
function translateSseStream(upstreamBody: ReadableStream<Uint8Array>, model: string, id: string, created: number): ReadableStream<Uint8Array> {
const decoder = new TextDecoder();
const encoder = new TextEncoder();
return new ReadableStream<Uint8Array>({
async start(controller) {
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
})
)
);
const reader = upstreamBody.getReader();
let buffer = "";
let finished = false;
const finish = () => {
if (finished) return;
finished = true;
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
})
)
);
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
};
const emitContent = (text: string) => {
if (!text) return;
controller.enqueue(
encoder.encode(
buildSseChunk({
id,
object: "chat.completion.chunk",
created,
model,
choices: [{ index: 0, delta: { content: text }, finish_reason: null }],
})
)
);
};
// SSE event framing: "event:"/"data:" lines, blank-line separated records.
const processEvent = (eventText: string) => {
let eventType = "message";
const dataLines: string[] = [];
for (const rawLine of eventText.split("\n")) {
if (rawLine.startsWith("event:")) {
eventType = rawLine.slice(6).trim();
} else if (rawLine.startsWith("data:")) {
dataLines.push(rawLine.slice(5).trim());
}
}
const data = dataLines.join("\n");
if (eventType === "content") {
try {
const parsed = asRecord(JSON.parse(data));
if (typeof parsed.content === "string") emitContent(parsed.content);
} catch {
// Ignore malformed content events rather than surfacing partial JSON.
}
} else if (eventType === "error") {
emitContent(`\n[1min.ai error: ${data}]`);
finish();
} else if (eventType === "done") {
finish();
}
// "result" carries the final full aiRecord, redundant with the content
// events already streamed — intentionally ignored.
};
try {
while (!finished) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let separatorIndex = buffer.indexOf("\n\n");
while (separatorIndex !== -1) {
processEvent(buffer.slice(0, separatorIndex));
buffer = buffer.slice(separatorIndex + 2);
separatorIndex = buffer.indexOf("\n\n");
}
}
if (!finished && buffer.trim()) processEvent(buffer);
finish();
} catch (error) {
controller.error(error);
} finally {
reader.releaseLock();
}
},
});
}
export class OneMinAiExecutor extends BaseExecutor {
constructor() {
super("oneminai", PROVIDERS["oneminai"] || { format: "openai", baseUrl: CHAT_URL });
}
buildUrl(_model: string, stream: boolean): string {
return stream ? `${CHAT_URL}?isStreaming=true` : CHAT_URL;
}
buildHeaders(credentials: ProviderCredentials | null): Record<string, string> {
const key = credentials?.apiKey || credentials?.accessToken || "";
return {
"Content-Type": "application/json",
"API-KEY": key,
};
}
transformRequest(model: string, body: unknown): JsonRecord {
const payload = asRecord(body);
const messages = Array.isArray(payload.messages) ? (payload.messages as OpenAIMessage[]) : [];
return {
type: "UNIFY_CHAT_WITH_AI",
model,
promptObject: { prompt: buildPrompt(messages) },
};
}
async execute({ model, body, stream, credentials, signal, upstreamExtraHeaders }: ExecuteInput) {
const url = this.buildUrl(model, stream);
const headers = this.buildHeaders(credentials);
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
const payload = this.transformRequest(model, body);
const id = `chatcmpl-oneminai-${randomUUID()}`;
const created = Math.floor(Date.now() / 1000);
try {
this.assertOutboundUrlAllowed(url);
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(payload),
signal,
});
if (!response.ok) {
const errorText = await response.text();
let message = `1min.ai API failed with status ${response.status}`;
try {
const parsed = asRecord(JSON.parse(errorText));
const err = asRecord(parsed.error);
if (typeof err.message === "string") message = err.message;
} catch {
if (errorText) message = errorText;
}
return {
response: toOpenAiErrorResponse(response.status, message),
url,
headers,
transformedBody: payload,
};
}
if (stream) {
if (!response.body) {
return {
response: toOpenAiErrorResponse(502, "1min.ai returned an empty stream"),
url,
headers,
transformedBody: payload,
};
}
return {
response: new Response(translateSseStream(response.body, model, id, created), {
status: 200,
headers: { "Content-Type": "text/event-stream" },
}),
url,
headers,
transformedBody: payload,
};
}
const json = asRecord(await response.json());
const aiRecord = asRecord(json.aiRecord);
const detail = asRecord(aiRecord.aiRecordDetail);
const resultObject = Array.isArray(detail.resultObject) ? detail.resultObject : [];
const content = resultObject.filter((part): part is string => typeof part === "string").join("");
return {
response: buildOpenAiJsonCompletion(content, model, id, created),
url,
headers,
transformedBody: payload,
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error || "Unknown error");
return {
response: toOpenAiErrorResponse(502, `1min.ai fetch error: ${message}`),
url,
headers,
transformedBody: payload,
};
}
}
}
export default OneMinAiExecutor;

View File

@@ -60,6 +60,11 @@ const STATIC_MODEL_PROVIDERS: Record<string, () => Array<{ id: string; name: str
id: model.id,
name: model.name || model.id,
})),
oneminai: () =>
getModelsByProviderId("oneminai").map((model) => ({
id: model.id,
name: model.name || model.id,
})),
qoder: () => getStaticQoderModels(),
// Non-LLM providers with no /v1/models endpoint — expose their selectable
// capability ids as a static catalog so the model-import step shows a usable

View File

@@ -76,6 +76,7 @@ import {
validateRekaProvider,
validateMaritalkProvider,
validateNlpCloudProvider,
validateOneMinAiProvider,
validateRunwayProvider,
validateNousResearchProvider,
validatePoeProvider,
@@ -297,6 +298,7 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi
reka: validateRekaProvider,
maritalk: validateMaritalkProvider,
nlpcloud: validateNlpCloudProvider,
oneminai: validateOneMinAiProvider,
runwayml: validateRunwayProvider,
snowflake: validateSnowflakeProvider,
gigachat: validateGigachatProvider,

View File

@@ -525,6 +525,58 @@ export async function validateNlpCloudProvider({ apiKey, providerSpecificData =
return { valid: false, error: "Connection failed while testing NLP Cloud" };
}
export async function validateOneMinAiProvider({ apiKey, providerSpecificData = {} }: any) {
const modelId =
typeof providerSpecificData.validationModelId === "string" &&
providerSpecificData.validationModelId.trim()
? providerSpecificData.validationModelId.trim()
: "gpt-4o-mini";
try {
const response = await validationWrite("https://api.1min.ai/api/chat-with-ai", {
method: "POST",
headers: { "Content-Type": "application/json", "API-KEY": apiKey },
body: JSON.stringify({
type: "UNIFY_CHAT_WITH_AI",
model: modelId,
promptObject: { prompt: "test" },
}),
});
if (response.ok) {
return { valid: true, error: null, method: "oneminai_chat_with_ai" };
}
if (response.status === 401 || response.status === 403) {
return { valid: false, error: "Invalid API key" };
}
if (response.status === 429) {
return {
valid: true,
error: null,
method: "oneminai_chat_with_ai",
warning: "Rate limited, but credentials are valid",
};
}
// 400/422 with a valid key still means the key authenticated — 1min.ai
// rejects an unrecognized/unauthorized model this same way as a bad
// request body, so a validation-shaped 4xx is treated as "key is valid".
if (response.status === 400 || response.status === 422) {
return { valid: true, error: null, method: "oneminai_chat_with_ai" };
}
if (response.status >= 500) {
return { valid: false, error: `Provider unavailable (${response.status})` };
}
} catch (error: any) {
return toValidationErrorResult(error);
}
return { valid: false, error: "Connection failed while testing 1min.ai" };
}
export async function validateRunwayProvider({ apiKey, providerSpecificData = {} }: any) {
const baseUrl = normalizeRunwayBaseUrl(providerSpecificData.baseUrl);

View File

@@ -3,6 +3,22 @@
* Pure data; merged by apikey/index.ts via spread (god-file decomposition; semantic split).
*/
export const APIKEY_PROVIDERS_GATEWAYS = {
// 1min.ai (https://docs.1min.ai) — multi-model chat aggregator with its own
// custom API (single `prompt` string + real SSE, not OpenAI-compatible).
// OmniRoute's oneminai executor translates both directions.
oneminai: {
id: "oneminai",
alias: "1min",
name: "1min.AI",
icon: "hub",
color: "#6366F1",
textIcon: "1M",
website: "https://1min.ai",
authHint: "Create an API key at https://docs.1min.ai/docs/api/create-api-key, then paste it here.",
apiHint:
"1min.ai uses a proprietary chat API (single prompt string + SSE) instead of OpenAI chat/completions. OmniRoute flattens OpenAI messages into a labeled prompt and translates the SSE stream.",
passthroughModels: true,
},
// Cheaper Inference (https://cheaperinference.com) — OSS-sponsor gateway.
// Cost-ranked reseller of 42 upstream models (Anthropic/OpenAI/Google/Moonshot/
// xAI/Z.AI/DeepSeek/MiniMax) behind one OpenAI-compatible surface, with a native

View File

@@ -1,5 +1,10 @@
{
"entries": {
"1min": {
"className": "OneMinAiExecutor",
"configSource": "oneminai",
"provider": "oneminai"
},
"9router": {
"className": "NineRouterExecutor",
"configSource": "<custom-config>",
@@ -485,6 +490,11 @@
"configSource": "<custom-config>",
"provider": "notion-web"
},
"oneminai": {
"className": "OneMinAiExecutor",
"configSource": "oneminai",
"provider": "oneminai"
},
"opencode": {
"className": "OpencodeExecutor",
"configSource": "opencode-zen",
@@ -711,6 +721,6 @@
"provider": "zai-web"
}
},
"keyCount": 142,
"keyCount": 144,
"sharedInstances": []
}

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
// R0.3 GOLDEN LOCK (characterization BEFORE the ExecutorRegistry refactor):
// freeze the full provider-id → executor mapping of open-sse/executors/index.ts —
@@ -34,7 +35,7 @@ test.after(() => {
// to (or removed from) the hard-coded map cannot hide from the snapshot.
function readSpecializedKeys(): string[] {
const src = fs.readFileSync(
path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../open-sse/executors/index.ts"),
path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../open-sse/executors/index.ts"),
"utf8"
);
const mapMatch = src.match(/const lazyExecutors[^\n]*= \{([\s\S]*?)\n\};/);

View File

@@ -0,0 +1,176 @@
import test from "node:test";
import assert from "node:assert/strict";
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
import { OneMinAiExecutor, buildPrompt } from "../../open-sse/executors/oneminai.ts";
const encoder = new TextEncoder();
function jsonResponse(body: unknown, status = 200) {
return new Response(JSON.stringify(body), {
status,
headers: { "Content-Type": "application/json" },
});
}
function sseResponse(events: string[]) {
return new Response(
new ReadableStream({
start(controller) {
for (const event of events) controller.enqueue(encoder.encode(event));
controller.close();
},
}),
{ status: 200, headers: { "Content-Type": "text/event-stream" } }
);
}
test("OneMinAiExecutor is registered in the executor index", async () => {
assert.equal(hasSpecializedExecutor("oneminai"), true);
assert.ok((await getExecutor("oneminai")) instanceof OneMinAiExecutor);
assert.equal(hasSpecializedExecutor("1min"), true);
assert.ok((await getExecutor("1min")) instanceof OneMinAiExecutor);
});
test("buildPrompt passes a single user message through unchanged", () => {
assert.equal(buildPrompt([{ role: "user", content: "Hello there" }]), "Hello there");
});
test("buildPrompt flattens multi-turn history into a labeled transcript", () => {
const prompt = buildPrompt([
{ role: "system", content: "You are concise." },
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there!" },
{ role: "user", content: "How are you?" },
]);
assert.equal(
prompt,
"System: You are concise.\n\nUser: Hello\n\nAssistant: Hi there!\n\nUser: How are you?"
);
});
test("OneMinAiExecutor sends UNIFY_CHAT_WITH_AI with a flattened prompt and API-KEY header, and unwraps the JSON response", async () => {
const executor = new OneMinAiExecutor();
const originalFetch = globalThis.fetch;
const calls: Array<{ url: string; body: Record<string, unknown>; headers: Record<string, string> }> =
[];
globalThis.fetch = async (url, init: RequestInit = {}) => {
calls.push({
url: String(url),
body: JSON.parse(String(init.body || "{}")),
headers: init.headers as Record<string, string>,
});
return jsonResponse({
aiRecord: {
model: "gpt-4o-mini",
status: "SUCCESS",
aiRecordDetail: {
promptObject: { prompt: "How are you?" },
resultObject: ["I'm doing well, thanks for asking!"],
},
},
});
};
try {
const result = await executor.execute({
model: "gpt-4o-mini",
body: {
messages: [
{ role: "system", content: "You are concise." },
{ role: "user", content: "Hello" },
{ role: "assistant", content: "Hi there!" },
{ role: "user", content: "How are you?" },
],
},
stream: false,
credentials: { apiKey: "1min-key" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(calls.length, 1);
assert.equal(calls[0].url, "https://api.1min.ai/api/chat-with-ai");
assert.equal(calls[0].headers["API-KEY"], "1min-key");
assert.equal(calls[0].body.type, "UNIFY_CHAT_WITH_AI");
assert.equal(calls[0].body.model, "gpt-4o-mini");
assert.equal(
(calls[0].body.promptObject as { prompt: string }).prompt,
"System: You are concise.\n\nUser: Hello\n\nAssistant: Hi there!\n\nUser: How are you?"
);
const body = await result.response.json();
assert.equal(body.object, "chat.completion");
assert.equal(body.choices[0].message.role, "assistant");
assert.equal(body.choices[0].message.content, "I'm doing well, thanks for asking!");
assert.equal(body.model, "gpt-4o-mini");
} finally {
globalThis.fetch = originalFetch;
}
});
test("OneMinAiExecutor requests the isStreaming=true endpoint and translates 1min.ai SSE into OpenAI chunks", async () => {
const executor = new OneMinAiExecutor();
const originalFetch = globalThis.fetch;
let requestedUrl = "";
globalThis.fetch = async (url) => {
requestedUrl = String(url);
return sseResponse([
'event: content\ndata: {"content": "Artificial intelligence is"}\n\n',
'event: content\ndata: {"content": " a branch of computer science."}\n\n',
'event: done\ndata: {"message": "Stream completed"}\n\n',
]);
};
try {
const result = await executor.execute({
model: "gpt-4o-mini",
body: { messages: [{ role: "user", content: "What is AI?" }] },
stream: true,
credentials: { apiKey: "1min-key" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(requestedUrl, "https://api.1min.ai/api/chat-with-ai?isStreaming=true");
assert.equal(result.response.headers.get("Content-Type"), "text/event-stream");
const text = await result.response.text();
assert.match(text, /data: \{"id":"chatcmpl-oneminai-/);
assert.match(text, /Artificial intelligence is/);
assert.match(text, /a branch of computer science\./);
assert.match(text, /"finish_reason":"stop"/);
assert.match(text, /data: \[DONE\]/);
} finally {
globalThis.fetch = originalFetch;
}
});
test("OneMinAiExecutor maps upstream auth failures to OpenAI-style errors", async () => {
const executor = new OneMinAiExecutor();
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
jsonResponse(
{ success: false, error: { code: "UNAUTHORIZED", message: "Invalid or missing API key" } },
401
);
try {
const result = await executor.execute({
model: "gpt-4o-mini",
body: { messages: [{ role: "user", content: "hi" }] },
stream: false,
credentials: { apiKey: "bad-key" },
signal: AbortSignal.timeout(10_000),
log: null,
});
assert.equal(result.response.status, 401);
const body = await result.response.json();
assert.match(body.error.message, /Invalid or missing API key/i);
} finally {
globalThis.fetch = originalFetch;
}
});