Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions packages/runtime/src/agent/__tests__/aimlapi-attribution.test.ts
Original file line number Diff line number Diff line change
@@ -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_B5Xmawp87YODJfuBUtiCbR2m")).toBe(true);
});
});
32 changes: 32 additions & 0 deletions packages/runtime/src/agent/aimlapi-attribution.ts
Original file line number Diff line number Diff line change
@@ -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 = "part_B5Xmawp87YODJfuBUtiCbR2m";

/**
* 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}$/;
Original file line number Diff line number Diff line change
Expand Up @@ -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), 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:

Expand All @@ -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.
</Callout>

<Callout type="warn" title="aimlapi.com: use provider.chat(model) for non-OpenAI ids">
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.
</Callout>

```typescript
import { BuiltInAgent } from "@copilotkit/runtime/v2";
import { createOpenAI } from "@ai-sdk/openai"; // [!code highlight]
Expand Down