Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a55e499
Port the Pydantic AI example to Pydantic AI v2
dsfaccini Aug 3, 2026
db1f877
feat(shell-docs): route every Channels entry point at one onboarding …
BenTaylorDev Aug 4, 2026
7f493d2
merge origin/codex/channels-prompt-visual-flare
BenTaylorDev Aug 4, 2026
e62d950
feat(shell-docs): give the Channels entry point #6356's featured trea…
BenTaylorDev Aug 4, 2026
55790bb
feat(shell-docs): name the real onboarding skill slug
BenTaylorDev Aug 4, 2026
3a2af9e
feat(shell-docs): add activation impressions, keep the prompt in the …
BenTaylorDev Aug 4, 2026
22abe9c
feat(shell-docs): lead with the copy action, drop the dangling instru…
BenTaylorDev Aug 4, 2026
4d5fd3a
fix(shell-docs): stop a throwing analytics client from faking a copy …
BenTaylorDev Aug 4, 2026
afef22d
feat(shell-docs): restore the prompt disclosure and put it on the doc…
BenTaylorDev Aug 4, 2026
335b12a
feat(shell-docs): point the Channels prompt at the hosted guide
BenTaylorDev Aug 4, 2026
503fc7c
fix(channels): back off prolonged outage logs
tylerslaton Aug 3, 2026
82eaecd
fix(channels): retry transient gateway activation
tylerslaton Aug 3, 2026
dfe728d
fix(channels): retry gateway drain joins
tylerslaton Aug 4, 2026
f8f9e2a
chore: release channels v0.7.1
tylerslaton Aug 4, 2026
d8eeb77
chore: release channels v0.7.1 (#6361)
tylerslaton Aug 4, 2026
a1d27ab
feat(shell-docs): strip both Channels surfaces to one copy action
BenTaylorDev Aug 4, 2026
c69f7e9
chore: release monorepo v1.66.1
tylerslaton Aug 4, 2026
d92652d
chore: release monorepo v1.66.1 (#6365)
tylerslaton Aug 4, 2026
397c8ce
docs: route every Channels entry point at one onboarding skill (#6357)
BenTaylorDev Aug 4, 2026
4c2ba2d
feat(skills): add channels-setup pointing at the hosted onboarding guide
BenTaylorDev Aug 4, 2026
4be2516
fix(examples): pin pydantic-ai starter deps and build deps per request
dsfaccini Aug 4, 2026
7b5cc8c
fix(channels): recover from transient gateway outages (#6347)
AlemTuzlak Aug 4, 2026
14a294f
Port the Pydantic AI example to Pydantic AI v2 (#6355)
contextablemark Aug 4, 2026
8e59bfd
feat(skills): add channels-setup pointing at the hosted onboarding gu…
BenTaylorDev Aug 4, 2026
86cd674
fix(runtime): ignore late lock renewal failures
tylerslaton Aug 4, 2026
ae0dbc2
feat(skills): make setup-slack-channel drive the browser by default
jerelvelarde Aug 4, 2026
7193856
fix(runtime): ignore late lock renewal failures (#6369)
tylerslaton Aug 4, 2026
7c5468e
fix(skills): ask for the decisions a driving agent must not invent
jerelvelarde Aug 4, 2026
f1b42e2
feat(skills): make setup-slack-channel drive the browser by default (…
tylerslaton Aug 4, 2026
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
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
},
"metadata": {
"description": "AI agent skills for CopilotKit — setup, develop, integrate, debug, upgrade, and contribute",
"version": "1.66.0"
"version": "1.66.1"
},
"plugins": [
{
"name": "copilotkit",
"source": "./",
"description": "AI agent skills for CopilotKit — setup, develop, integrate, debug, upgrade, and contribute to CopilotKit projects",
"version": "1.66.0",
"version": "1.66.1",
"author": {
"name": "CopilotKit",
"url": "https://copilotkit.ai"
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "copilotkit",
"description": "AI agent skills for CopilotKit — setup, develop, integrate, debug, upgrade, and contribute to CopilotKit projects",
"version": "1.66.0",
"version": "1.66.1",
"author": {
"name": "CopilotKit",
"url": "https://copilotkit.ai"
Expand Down
4 changes: 2 additions & 2 deletions examples/integrations/pydantic-ai/agent/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"uvicorn",
"pydantic-ai-slim[ag-ui,openai]==1.0.18",
"ag-ui-protocol==0.1.18",
"pydantic-ai-slim[ag-ui,openai]==2.22.0",
"ag-ui-protocol==0.1.19",
"python-dotenv",
"logfire>=4.10.0",
]
2 changes: 1 addition & 1 deletion examples/integrations/pydantic-ai/agent/src/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from textwrap import dedent
from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
from pydantic_ai.ag_ui import StateDeps
from pydantic_ai.ui import StateDeps
from ag_ui.core import EventType, StateSnapshotEvent
from pydantic_ai.models.openai import OpenAIResponsesModel

Expand Down
16 changes: 12 additions & 4 deletions examples/integrations/pydantic-ai/agent/src/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
from agent import ProverbsState, StateDeps, agent
from pydantic_ai.ui.ag_ui import AGUIAdapter
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import Route

agui_app = agent.to_ag_ui(deps=StateDeps(ProverbsState()))

async def run_agent(request: Request) -> Response:
# Fresh deps per request: `dispatch_request` writes the client's state into
# `deps.state`, so a shared instance leaks state between threads, channels and users.
return await AGUIAdapter.dispatch_request(
request, agent=agent, deps=StateDeps(ProverbsState())
)


async def health(request):
Expand All @@ -13,7 +21,7 @@ async def health(request):
app = Starlette(
routes=[
Route("/health", health),
Mount("/", app=agui_app),
Route("/", run_agent, methods=["POST"]),
]
)

Expand Down
298 changes: 233 additions & 65 deletions examples/integrations/pydantic-ai/agent/uv.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/a2ui-renderer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/a2ui-renderer",
"version": "1.66.0",
"version": "1.66.1",
"private": false,
"description": "A2UI Renderer for CopilotKit - render A2UI surfaces in React applications",
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion packages/agentcore-runner/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/agentcore-runner",
"version": "1.66.0",
"version": "1.66.1",
"description": "AWS Bedrock AgentCore-compatible agent runner for CopilotKit2",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/channels-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/channels-core",
"version": "0.7.0",
"version": "0.7.1",
"description": "Platform-agnostic JSX channel engine for CopilotKit (createChannel, Thread, PlatformAdapter, ActionStore).",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/channels-discord/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/channels-discord",
"version": "0.7.0",
"version": "0.7.1",
"description": "Discord PlatformAdapter for @copilotkit/channels.",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/channels-intelligence/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@copilotkit/channels-intelligence",
"version": "0.7.0",
"version": "0.7.1",
"description": "Intelligence-delivered Channel adapter for CopilotKit JSX channels (@copilotkit/channels).",
"license": "MIT",
"repository": {
Expand Down
1 change: 1 addition & 0 deletions packages/channels-intelligence/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type {

export {
connectRealtimeGateway,
RealtimeGatewayJoinError,
RealtimeGatewayUnreachableError,
} from "./realtime-gateway.js";
export type {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { expect, test, vi } from "vitest";
import {
connectRealtimeGateway,
RealtimeGatewayUnreachableError,
} from "./realtime-gateway.js";

interface RetryabilitySetup {
connection: ReturnType<typeof connectRealtimeGateway>;
diagnosticFetch: ReturnType<typeof vi.fn<typeof fetch>>;
}

/** Build one socket that opens but rejects or never answers its join. */
function setupJoinFailure(
response?: Record<string, unknown>,
): ReturnType<typeof connectRealtimeGateway> {
class RejectedWebSocket {
static readonly CONNECTING = 0;
static readonly OPEN = 1;
static readonly CLOSING = 2;
static readonly CLOSED = 3;
readyState = RejectedWebSocket.CONNECTING;
binaryType = "arraybuffer";
onopen: (() => void) | null = null;
onmessage: ((event: { data: string }) => void) | null = null;
onerror: ((error: Error) => void) | null = null;
onclose: ((event: { code: number }) => void) | null = null;

constructor() {
queueMicrotask(() => {
this.readyState = RejectedWebSocket.OPEN;
this.onopen?.();
});
}

send(data: string): void {
const frame = JSON.parse(data) as [string, string, string, string];
const [joinRef, ref, topic, event] = frame;
if (event !== "phx_join" || response === undefined) return;
queueMicrotask(() =>
this.onmessage?.({
data: JSON.stringify([
joinRef,
ref,
topic,
"phx_reply",
{ status: "error", response },
]),
}),
);
}

close(): void {
this.readyState = RejectedWebSocket.CLOSED;
queueMicrotask(() => this.onclose?.({ code: 1000 }));
}
}

return connectRealtimeGateway({
wsUrl: "wss://gateway.example/channels",
apiKey: "cpk-test",
projectId: 7,
join: {
protocol: "channel_delivery_v1",
runtimeInstanceId: "rti_1",
channels: [{ channelName: "support", adapter: "slack" }],
},
timeoutMs: 10,
webSocket: RejectedWebSocket,
});
}

/** Build one socket that cannot upgrade and an HTTP diagnosis for its host. */
function setupRetryability(
status: number,
transportError: Error = new Error("upgrade refused"),
): RetryabilitySetup {
class RefusedWebSocket {
static readonly CONNECTING = 0;
static readonly OPEN = 1;
static readonly CLOSING = 2;
static readonly CLOSED = 3;
readyState = RefusedWebSocket.CONNECTING;
binaryType = "arraybuffer";
onopen: (() => void) | null = null;
onmessage: ((event: { data: string }) => void) | null = null;
onerror: ((error: Error) => void) | null = null;
onclose: ((event: { code: number }) => void) | null = null;

constructor() {
queueMicrotask(() => this.onerror?.(transportError));
}

send(): void {}

close(): void {
this.readyState = RefusedWebSocket.CLOSED;
queueMicrotask(() => this.onclose?.({ code: 1000 }));
}
}

const diagnosticFetch = vi.fn<typeof fetch>(async () =>
Promise.resolve(new Response(null, { status })),
);
const connection = connectRealtimeGateway({
wsUrl: "wss://gateway.example/channels",
apiKey: "cpk-test",
projectId: 7,
join: {
protocol: "channel_delivery_v1",
runtimeInstanceId: "rti_1",
channels: [{ channelName: "support", adapter: "slack" }],
},
connectTimeoutMs: 10,
diagnosticFetch,
webSocket: RefusedWebSocket,
});

return { connection, diagnosticFetch };
}

test("an HTTP 502 gateway diagnosis is retryable", async () => {
const { connection, diagnosticFetch } = setupRetryability(502);

const error = await connection.catch((cause: unknown) => cause);

expect(diagnosticFetch).toHaveBeenCalledOnce();
expect(error).toBeInstanceOf(RealtimeGatewayUnreachableError);
expect((error as RealtimeGatewayUnreachableError).retryable).toBe(true);
});

test("an HTTP 403 gateway diagnosis is terminal", async () => {
const { connection, diagnosticFetch } = setupRetryability(403);

const error = await connection.catch((cause: unknown) => cause);

expect(diagnosticFetch).toHaveBeenCalledOnce();
expect(error).toBeInstanceOf(RealtimeGatewayUnreachableError);
expect((error as RealtimeGatewayUnreachableError).retryable).toBe(false);
});

test("an ENOTFOUND transport failure is terminal without an HTTP probe", async () => {
const transportError = Object.assign(
new Error("getaddrinfo ENOTFOUND gateway.example"),
{ code: "ENOTFOUND" },
);
const { connection, diagnosticFetch } = setupRetryability(
502,
transportError,
);

const error = await connection.catch((cause: unknown) => cause);

expect(diagnosticFetch).not.toHaveBeenCalled();
expect(error).toBeInstanceOf(RealtimeGatewayUnreachableError);
expect((error as RealtimeGatewayUnreachableError).retryable).toBe(false);
});

test("a gateway_draining join rejection preserves its retry classification", async () => {
const response = { reason: "gateway_draining", retryable: true };

const error = await setupJoinFailure(response).catch(
(cause: unknown) => cause,
);

expect(error).toMatchObject({
code: "GATEWAY_JOIN_FAILED",
reason: response,
retryable: true,
});
expect((error as Error).message).toMatch(/gateway_draining/);
});

test("an initial gateway join timeout is retryable", async () => {
const error = await setupJoinFailure().catch((cause: unknown) => cause);

expect(error).toMatchObject({
code: "GATEWAY_JOIN_FAILED",
retryable: true,
});
expect((error as Error).message).toMatch(/join timed out/);
});
Loading
Loading