Last week, I became a meme.
ā
I posted a screenshot of some code powerful (yet unphotogenic) code that was co-created by a CopilotKit engineer andā¦ by a Copilot (Cursor IDEās).
I had a feeling it would be polarizing - and truth be told I leaned into that slightly (as youāll see belowā¦). But didnāt think it would become a niche coding meme!
But as I said last week, this is good code and I will die on that hill!
Itās also a fascinating piece of code - and on first glance, itās a little surprising that what it enables is even possible.
Hereās why š
But first: the memes
My tweet got dunked on quite a few times, by a few familiar faces:
There were some pretty funny memes:
And a few based takes:
My own overall responseā¦š:
With the memes behind us (for now),
letās see why: (itās pretty interesting!)
First of all, what does this code do?
Essentially it's a if/else tree with some recursion - for figuring out the types of variables in user code during build time.
(in other words itās a conditional, recursive type expression).
Worth noting off the bat - that type expressions code doesnāt actually run as part of the application - and so cannot contain if/else blocks, only the ternary operator.
Why does it exist?
In a moment weāll get to a detailed analysis of how this code works. But first letās see what it enables.
Background: CopilotKit
CopilotKit is an open-source platform providing plug-and-play, fully customizable Copilot building blocks-
for building app-aware AI chatbots, AI Textareas, and AI agents that can interact with your app - based on application context (and quite a bit more).
You can bring your own LLM (GPT-4 by default), and as of 2 weeks ago, you can bring your own LangChains & LangGraphs too as Copilot Skills.
useCopilotAction
One of CopilotKitās frontend features is the useCopilotAction
react hook. Each component in the application can define high-level actions for Copilot interactivity.
A simple example:
a food delivery app could add support for addToCurrentOrder
which specifies accepting a dishName
and quantity
arguments, and then allow users to ask for āa starter order for a very hungry family of 5, with 2 picky children, mostly but not only vegetarianā.
Specifying this in code is extremely straightforward. And it works! The Copilot engine is aware of the variables, their types, etc. - and calls the handler as needed (plain typescript/javascript code)
The only problem: the type information isnāt propagated into user code. dishName
, quantity
etc., all appear as any
.
Furthermore: if users of the framework make even a minor error - e.g. if they specify the variable names in the wrong order, or with a small typo ā their end-users are going to get a runtime error. š±
This gets quite a bit worse when you go beyond the simplest case - with nested objects, arrays, etc:
What about Zod Schema?
We have a variant of this react hook which does use Zod!
But in talking to users, a significant portion of them had not used Zod before, or did not like it. Itās cognitive overhead that we donāt want to force on users.
And truth be told: I get it.
As you saw in The Screenshotā¦ we like types, and we can handle the tradeoffs.
But the code below is not an ideal dev experience - especially for the uninitiated.
Zod should not be a CopilotKit adoption blocker.
A Free Lunch! (for our users)
Ironically, the whole point of this scary complex āsuperior mega brainedā type code isā¦ to let the āanti-typistsā have their cake and eat it too.
We realized that we can provide all of the safety + convenience benefits of Zod to those who choose plain JSON schemas - with zero overhead for users of the framework.
And all it takes isā¦ 30 lines of conditional recursive type dust in the framework.
In the service of making *user-code* as simple and pedestrian as possible - good framework code can ā and routinely does ā pull a few tricks.
Framework engineers know this!
(What about debugging, etc? As youāll see below, this code is really not so complex; it is hard to write, but itās not too hard to read. But furthermore type expressions are structurally some of the safest code imaginable: they only affects types, are verified by the compiler, and they even get ācompiledā out of existence on the executable code.)
Hereās what this code enables, as you can see in the image below:
- type information is propagated ā
- variables can be specified in any order (they match on names, not positions) ā
- typos are statically called out as type errors ā
- This also works recursively with objects and arrays: ā
As framework authors, our users' dev experience >> our own dev experience. We can handle 30 lines of complex type code (that we understand well). The upside is a magical dev experience for thousands of devs every day. Obviously, this is the right tradeoff.
Bonus: render
By the way, you might have noticed the render
function sitting after the handler. Yes: actions can render arbitrary react components into the chat window.
And of course: the action types continue to hold over there (with even more structure - e.g. they are automatically designated as optional when the function status is āstill-streamingā).
Special call-out to Aceternity UI (open-source)- we made the sexy function preview component below in minutes, just for this post)
THE Codeā¦š„
The dunks and the memes were honestly hilarious.
Good job fellas š
But for all the geniuses who dunked on the codeā¦
please indulge me a small counter-dunk:
Now, you know what we need:
To get type information out of the raw JSONs above, with support for recursion, arbitrary parameter ordering, typo detection, etc.
Letās be honest for a moment:
how many of us could write this actual feature (not a āclose-enoughā demo) from scratch? Even after seeing the code?
I stand by my meme- VERY few! š (if you can do it - we are hiring).
And if few could implement it - fewer still would implement it (I certainly wouldnāt).
How does it work?
Mea Culpa
First a small Mea Culpa - I am guilty!
The GitHub PR indentation makes the code appear more monolithic than it is.
In reality there 4 clearly distinct parts here - and the code is not that hard to understand.
As I alluded to earlierā¦ this is not entirely accidental. Twitter is a funny world: things need to have meme-punch or they get 0 visibility. Thatās the game. I purposefully chose the GitHub PR screenshot (not an IDE screenshot), where indentation is less clear- I wanted a little bit of a shock factor.
So - I deserve the roast.
(Iād hoped folks would see whatās going on on 2nd glance. A handful didā¦ š
)
But there really is something special going on here. This is a piece of code which significantly boosts the developer experience - which would likely not have been written (by us) in a GPT-less world. So I stand by it: the intellectual ceiling is being raised.
Had the code looked visually boring at a glance (rather than striking) - it would not have done justice to this singular moment in the history of technological evolution.
Letās look at the code:
Below is exactly the same code, with more visible indents (and 4 newlines - sue me š).
Once again, itās a recursive + conditional type expression.
A couple of implications of this being a type expression:
- In type expressions, if/else is not available - you have to use the ternary operator.
- All type code gets ācompiledā out of existence on builds. Itās entirely about the developer experience.
If you zoom out, youāll see this is a simple if / else with 4 distinct cases (implemented via the ternary operator):
- enums (one of a few fixed strings - e.g. āinternationalā or ādomesticā).
- a single object
- an array of objects (with/without attributes specified- subtle difference for another post)
- the base case: specific parameters, can be required or optional
The object cases recurse back on MappedParameterTypes
for each of its own propertiesā types.
A second Mea Culpa?
Truth be told, I also had another iteration of this code which is even more actually complex, but also more boring-looking version when I tweeted The Screenshot. (and ā you guessed it ā the Copilot helped us arrive at this version in minutes.).
Hereās this version:
I suspect DHH would be even less happy with this code if he read through it - types galore. And yetā¦ it doesnāt quite pack the same visual punch. Iād also be first to admit there are also some abstraction complexity tradeoffs here - one could argue itās not a net improvement over the original version.
As for us?
If 30 lines of complex type code can make our usersā dev experience substantially better - weāll take that trade any day. Most frameworks have some code of this sort (at least the good ones).
In fact I personally pushed to merge this to main asap despite a few spots for minor improvement. Meta-infra PRs of this sort have a tendency to grow stale and never be merged ā unless they are merged rapidly. We are a startup, and we ship like hell - stale is not an option.
If you are excited by what human / AI collaboration can achieve, and if you leave no stone unturned in service of a better developer experience, we are hiring. Bonus points for extensive Cloud Native experience: we are building Copilot Cloud.
Personal remarks
GPT didnāt come up with the core idea for how to implement this. In fact it initially resisted our engineer who had the idea - told him itās not possible. And it didnāt get it working the first time either (or the 3rd).
This was a deeply collaborative exercise.
Where GPT tech was helpful - is in taking the abstract core insight - and applying it while taking lots of details into account.
This makes the situation even more special if you pause to think for a moment. Todaysā AI canāt do the deep thinking for us humans - but it CAN take care of minutia at all levels of abstraction, and help us focus on the most interesting parts of our work. Truly - Steve Jobsā ābicycle for the mindā.
Enjoy this moment while it lasts.
Ceiling is being raised: The $1000 Prize Competition!
I said itās good code - not that itās perfect code.
In between the dunks, a few folks on twitter had some legitimately interesting ideas for improvements. Cause for a competition!
We actually have yet to release a public version of CopilotKit that includes this change (if you want to use it, clone master
) - which makes for a natural competition deadline!
Submit a PR to our repo your best version of this code. Winner gets $1000 and eternal glory via a PR merge. Bonus points if some GPT / Copilot helps you arrive there and if you share the screenshots.
The PR must be submitted sufficiently early to make it to our next release. 1-2 weeks, but no exact date. So hurry up!
āTill next time.
ā
ā
ā
Get notified of the latest news andĀ updates.