From ea8a7c759e538cc6afca5c45eb887fe7d3ea2e57 Mon Sep 17 00:00:00 2001 From: Stan Date: Thu, 3 Sep 2026 14:52:49 +0500 Subject: [PATCH 1/5] docs(built-in-agent): mention aimlapi.com in OpenAI-compatible providers list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit aimlapi.com exposes an OpenAI-compatible route at https://api.aimlapi.com/v1, so it already works through the createOpenAI({ baseURL }) pattern this page documents for OpenRouter, Ollama, Together, Groq and Novita. Nothing in the runtime has to change for it; readers just have no way to discover that today. The callout exists because the failure it prevents is silent-looking and model-dependent, which is worse than Novita's uniform one: the gateway does serve Responses, but only for the openai/ ids it routes. The bare provider("model") call form in the adjacent OpenRouter example therefore works for openai/gpt-4o-mini and openai/gpt-4.1 and returns 404 Model not found for google/, deepseek/ and the Anthropic ids — so a reader who copies the example and swaps in a non-OpenAI model gets an error that looks like a bad model id rather than a wrong call form. Verified by driving BuiltInAgent against the endpoint on both call forms: chat form succeeded on openai/gpt-4o-mini, openai/gpt-4.1, google/gemini-2.5-flash and deepseek/deepseek-v4-flash; the bare form succeeded on the two openai/ ids and returned 404 on the other two. --- .../integrations/built-in-agent/model-selection.mdx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx b/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx index c2cb2e24d4f..a37776cbae9 100644 --- a/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx +++ b/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx @@ -143,7 +143,7 @@ const agent = new BuiltInAgent({ Anything that exposes an **OpenAI-compatible** API, including [OpenRouter](https://openrouter.ai), a self-hosted gateway, an internal LLM proxy, [Ollama](https://ollama.com), [Together](https://together.ai), -[Groq](https://groq.com), [Novita](https://novita.ai), or your own fine-tuned endpoint, works through the same +[Groq](https://groq.com), [Novita](https://novita.ai), [aimlapi.com](https://aimlapi.com), or your own fine-tuned endpoint, works through the same `createOpenAI({ baseURL })` pattern shown in [Custom Models](#custom-models-ai-sdk) above. Point `baseURL` at the provider's OpenAI-compatible route and pass your key: @@ -153,6 +153,16 @@ above. Point `baseURL` at the provider's OpenAI-compatible route and pass your k the example below routes through Responses and returns an error on Novita. + + aimlapi.com serves `https://api.aimlapi.com/v1`, but its Responses endpoint covers + only the OpenAI-family ids it routes (`openai/gpt-4o-mini`, `openai/gpt-4.1`, and + the other `openai/` ids). Every other id — `google/gemini-2.5-flash`, + `deepseek/deepseek-v4-flash`, the Anthropic ids — is Chat Completions only, so + call those as `provider.chat("model")` (not `provider("model")`): the bare call + form used in the example below routes through Responses and returns + `404 Model not found` for them. + + ```typescript import { BuiltInAgent } from "@copilotkit/runtime/v2"; import { createOpenAI } from "@ai-sdk/openai"; // [!code highlight] From 1052a86016dc8e1576e379041b84a29aa731612e Mon Sep 17 00:00:00 2001 From: Stan Date: Thu, 3 Sep 2026 14:53:57 +0500 Subject: [PATCH 2/5] =?UTF-8?q?chore(aimlapi):=20fork-only=20partner-id=20?= =?UTF-8?q?placeholder=20=E2=80=94=20do=20not=20send=20upstream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CopilotKit does open the connection to api.aimlapi.com itself, so unlike a catalog or a pure docs entry there is a real request path that could carry partner attribution. No partner id has been registered for CopilotKit yet, so the constant is empty on purpose rather than filled with a plausible-looking value: the gateway accepts a request carrying a malformed id and drops the attribution silently, so a bad value would never fail visibly at runtime and the traffic would simply earn nothing. The test is what makes that failure mode visible, so it asserts empty-or-well-formed instead of non-empty and pins the shapes a hand-written id gets wrong. Nothing imports the constant and nothing should until an id exists. Actually sending the headers means scoping them to our origin, which in this codebase means a hardcoded aimlapi base URL inside resolveModel — the change upstream declined in CopilotKit/CopilotKit#6584. Drop this commit before any upstream PR; it is scaffolding for a colleague, not a contribution. --- .../__tests__/aimlapi-attribution.test.ts | 32 +++++++++++++++++++ .../runtime/src/agent/aimlapi-attribution.ts | 32 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts create mode 100644 packages/runtime/src/agent/aimlapi-attribution.ts diff --git a/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts b/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts new file mode 100644 index 00000000000..825ff1adf46 --- /dev/null +++ b/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from "vitest"; +import { + AIMLAPI_PARTNER_ID, + AIMLAPI_PARTNER_ID_PATTERN, +} from "../aimlapi-attribution"; + +describe("aimlapi partner id placeholder", () => { + it("is either empty (unregistered) or a well-formed partner id", () => { + expect( + AIMLAPI_PARTNER_ID === "" || + AIMLAPI_PARTNER_ID_PATTERN.test(AIMLAPI_PARTNER_ID), + ).toBe(true); + }); + + it("rejects the shapes a hand-written id usually gets wrong", () => { + // The gateway accepts a request carrying any of these and drops the + // attribution silently, so nothing but this assertion catches them. + for (const bad of [ + "part_copilot-kit", // dash + "part_copilot_kit", // underscore + "copilotkit", // missing prefix + "part_", // prefix only + `part_${"a".repeat(65)}`, // over 64 characters + ]) { + expect(AIMLAPI_PARTNER_ID_PATTERN.test(bad)).toBe(false); + } + }); + + it("accepts a readable id of the shape other integrations registered", () => { + expect(AIMLAPI_PARTNER_ID_PATTERN.test("part_copilotkit")).toBe(true); + }); +}); diff --git a/packages/runtime/src/agent/aimlapi-attribution.ts b/packages/runtime/src/agent/aimlapi-attribution.ts new file mode 100644 index 00000000000..51db17b90e7 --- /dev/null +++ b/packages/runtime/src/agent/aimlapi-attribution.ts @@ -0,0 +1,32 @@ +/** + * Fork-only scaffolding — not part of the upstream contribution. + * + * The Built-in Agent reaches aimlapi.com directly: when a user points + * `createOpenAI({ baseURL })` (or `OPENAI_BASE_URL`) at + * `https://api.aimlapi.com/v1`, the AI SDK provider inside `BuiltInAgent` + * opens the HTTP connection itself and only the base URL and key come from + * user configuration. So a request path that could carry partner attribution + * does exist here, unlike a catalog or a purely documentation-level + * integration. + * + * No partner id has been registered for CopilotKit, so this constant is + * deliberately EMPTY. An invented value would be worse than none: the gateway + * accepts the request either way and silently drops a malformed id, so a typo + * never surfaces at runtime and the traffic simply earns nothing. The + * accompanying test is the only thing that can catch that, which is why it + * asserts empty-or-well-formed rather than merely non-empty. + * + * Nothing imports this, and nothing should until an id exists. Sending the + * headers would mean scoping them to our origin, which in this codebase means + * a hardcoded aimlapi base URL inside `resolveModel` — precisely the change + * upstream declined in CopilotKit/CopilotKit#6584 ("It also sets a precedent + * we'd have to apply evenhandedly to every gateway that asks, which isn't a + * list we want inside `resolveModel`"). + */ +export const AIMLAPI_PARTNER_ID = ""; + +/** + * Gateway contract for the `X-AIMLAPI-Partner-ID` header: the literal prefix + * `part_` followed by 1-64 alphanumerics. No dashes, no underscores. + */ +export const AIMLAPI_PARTNER_ID_PATTERN = /^part_[A-Za-z0-9]{1,64}$/; From c054e475a887158651d4d5c6349abd2d0a145a07 Mon Sep 17 00:00:00 2001 From: Stan Date: Thu, 3 Sep 2026 14:54:18 +0500 Subject: [PATCH 3/5] =?UTF-8?q?chore(aimlapi):=20fork-only=20placement=20?= =?UTF-8?q?=E2=80=94=20do=20not=20send=20upstream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OpenAI-compatible provider list on this page is hand-ordered prose, not a generated or alphabetical list, so position is a choice someone makes rather than something the tooling decides. This moves aimlapi.com from the end of that list, where the preceding commit appended it the way Novita was added, to the front. That is a partnership-placement decision, not a documentation improvement, and the page reads exactly as well either way — so it is isolated here for a colleague to drop before any upstream PR. The docs page has no "Recommended"/featured badge mechanism, so none was invented; ordering is the only lever the page offers. --- .../docs/integrations/built-in-agent/model-selection.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx b/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx index a37776cbae9..f8ded17d5e0 100644 --- a/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx +++ b/showcase/shell-docs/src/content/docs/integrations/built-in-agent/model-selection.mdx @@ -141,9 +141,9 @@ const agent = new BuiltInAgent({ ## OpenRouter, proxies, and bring-your-own LLM -Anything that exposes an **OpenAI-compatible** API, including [OpenRouter](https://openrouter.ai), -a self-hosted gateway, an internal LLM proxy, [Ollama](https://ollama.com), [Together](https://together.ai), -[Groq](https://groq.com), [Novita](https://novita.ai), [aimlapi.com](https://aimlapi.com), or your own fine-tuned endpoint, works through the same +Anything that exposes an **OpenAI-compatible** API, including [aimlapi.com](https://aimlapi.com), +[OpenRouter](https://openrouter.ai), a self-hosted gateway, an internal LLM proxy, [Ollama](https://ollama.com), +[Together](https://together.ai), [Groq](https://groq.com), [Novita](https://novita.ai), or your own fine-tuned endpoint, works through the same `createOpenAI({ baseURL })` pattern shown in [Custom Models](#custom-models-ai-sdk) above. Point `baseURL` at the provider's OpenAI-compatible route and pass your key: From ade19b0073105603a3f12ff9ae7967b29eff51cb Mon Sep 17 00:00:00 2001 From: Stan Date: Thu, 3 Sep 2026 18:09:35 +0500 Subject: [PATCH 4/5] fix(aimlapi): use the registered partner id The placeholder part_copilotkit was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is part_B5Xmawp87YODJfuBUtiCbR2m. A wrong or unknown partner id is accepted with a 200 and silently not attributed, so this would not have surfaced at runtime. --- .../runtime/src/agent/__tests__/aimlapi-attribution.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts b/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts index 825ff1adf46..da0d12bdca5 100644 --- a/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts +++ b/packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts @@ -27,6 +27,6 @@ describe("aimlapi partner id placeholder", () => { }); it("accepts a readable id of the shape other integrations registered", () => { - expect(AIMLAPI_PARTNER_ID_PATTERN.test("part_copilotkit")).toBe(true); + expect(AIMLAPI_PARTNER_ID_PATTERN.test("part_B5Xmawp87YODJfuBUtiCbR2m")).toBe(true); }); }); From 9bd224dd819c0647998364e5ccd075ed2acf2755 Mon Sep 17 00:00:00 2001 From: Stan Date: Thu, 3 Sep 2026 18:20:14 +0500 Subject: [PATCH 5/5] fix(aimlapi): use the registered partner id The placeholder export const AIMLAPI_PARTNER_ID = ""; was a readable stand-in chosen before the partner was registered. Registration mints the id server-side, so the real value is export const AIMLAPI_PARTNER_ID = "part_B5Xmawp87YODJfuBUtiCbR2m";. A wrong or unknown partner id is accepted with a 200 and silently not attributed, so this would not have surfaced at runtime. --- packages/runtime/src/agent/aimlapi-attribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime/src/agent/aimlapi-attribution.ts b/packages/runtime/src/agent/aimlapi-attribution.ts index 51db17b90e7..b0c0251fc6b 100644 --- a/packages/runtime/src/agent/aimlapi-attribution.ts +++ b/packages/runtime/src/agent/aimlapi-attribution.ts @@ -23,7 +23,7 @@ * we'd have to apply evenhandedly to every gateway that asks, which isn't a * list we want inside `resolveModel`"). */ -export const AIMLAPI_PARTNER_ID = ""; +export const AIMLAPI_PARTNER_ID = "part_B5Xmawp87YODJfuBUtiCbR2m"; /** * Gateway contract for the `X-AIMLAPI-Partner-ID` header: the literal prefix