Back
BY
Atai Barkai
and
July 8, 2024

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ā€¦šŸ˜‰:

Yes, you are all wrong Meme Generator - Imgflip

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ā€.

Users of the application would get their cart pre-populated with a base order - which they could then edit via application UX.

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.

ā€

ā€

ā€

Subscribe to the newsletter

Get notified of the latest news andĀ updates.