# 14.8 Reading SDK-shaped code (/building-ai-workers/structuring-programs/reading-sdk-code)

---
type: Document
title: "14.8 Reading SDK-shaped code"
description: "How to recognize the shapes in the code a company publishes for its service, and find what that code checks for you and what it leaves to you."
status: stable
order: 314.08
ksor:
  owner: team:panaversity
  audience: [ public ]
  approval:
    by: process:panaversity
    at: 2026-11-10T15:33:14Z
chapter: "14"
part: III
expert_status: builder-only
concepts: [ "14.8" ]
last_verified: 2026-10-30
generated:
  at: 2026-11-10T15:33:14Z
  by: process:panaversity
trust_tier: unverified
build_id: sha256:46fd53c5d7ab1c14f6347908d45c9fdaecda4d914439923ee878bdcecd57803e
dirty: true
ksor_version: 0.0.60
---

**In everyday life.** You can drive a rental car you have never seen before. You recognize the shapes: the pedals, the wheel, the lights. You do not need to know how the engine was built. But before you drive off, you check what the company left to you, such as the fuel and the spare tire.

**What you will learn.** How to recognize the shapes in the code a company publishes for its service, then find what that code checks for you and what it leaves to you.

**Why it matters.** From [Chapter 17](../connectors-and-mcp/overview.md) on, much of the code at your [edges](data-models-and-schemas.md#why-check-at-the-edge) is someone else's, built from a few shapes.

### What is an SDK?

An **SDK**, a software development kit, is a package a company publishes so programs can use its service: an AI model, a payment system, a database. SDKs use a few shapes, and agents write code in the same style.

### Which shapes will I meet?

Mostly these seven. You do not need to follow every line of the code below. Find each shape, and say what it does.

| Shape | Recognize it by | Read it as |
| --- | --- | --- |
| A client class | `new Client(...)` or `Client(...)` with a key and a URL | One object that holds the connection and the settings |
| An async method | `async` before a method | A call that waits for the network |
| A **generator** | `yield` inside a function, or `function*` | Hands out results one at a time, a stream, not all at once |
| An async iterator | `for await` in TypeScript, `async for` in Python | Loops over results as they arrive |
| A **context manager** | `with` or `async with` in Python, `using` or `await using` in TypeScript | Opens something and closes it for you, even after an error, which then still reaches you |
| A **decorator** | A line starting with `@` above a function or a class, in Python | Wraps it with extra behavior, such as a retry. `@dataclass` in [14.3](classes-and-objects.md) is one |
| A wrapper function | A function that takes a function and returns a new one | The same idea as a decorator, written by hand |

**At work.** Brightline's repository includes a 50-line made-up "AP SDK" in each language, shaped like the ones companies publish.

An excerpt, then its run against the [sample API](json-and-rest-apis.md#what-are-the-parts-of-a-request). Find the generator, the context manager, and the wrapper or decorator. Skip any mark you have not met. The comments explain the few you need.

```ts tab="TypeScript" tab-group="language"
export class ApClient {
  vendors = withRetry(() => this.#get("/vendors"));

  async *openInvoices(): AsyncGenerator<Invoice> {
    for (let page = 1; ; page++) { // counts 1, 2, 3, with no end of its own
      const body = await this.#get(`/invoices?status=open&page=${page}`);
      yield* body.invoices;
      if (body.page >= body.pages) return; // so return ends it
    }
  }
  // ...and one method that closes the client and prints "client closed"
}

await using client = new ApClient("http://localhost:4010", apiKey);
const { vendors } = await client.vendors(); // takes that one field from the answer
console.log(`${vendors.length} vendors`);
for await (const invoice of client.openInvoices()) console.log(invoice.number, invoice.amountCents);
```

```python tab="Python" tab-group="language"
# Self, Any and *exc are details you can ignore here
class ApClient:
    async def __aenter__(self) -> Self:
        return self

    async def __aexit__(self, *exc: object) -> None:
        await self._http.aclose()
        print("client closed")

    @retry_once
    async def vendors(self) -> list[dict[str, Any]]:
        response = await self._http.get("/vendors")
        return response.raise_for_status().json()["vendors"]

    async def open_invoices(self) -> AsyncIterator[dict[str, Any]]:
        page = 1
        while True:
            body = (await self._http.get(f"/invoices?status=open&page={page}")).json()
            for invoice in body["invoices"]:
                yield invoice
            if body["page"] >= body["pages"]:
                return
            page += 1

async def main() -> None:
    async with ApClient("http://localhost:4010", os.environ["AP_API_KEY"]) as client:
        print(f"{len(await client.vendors())} vendors")
        async for invoice in client.open_invoices():
            print(invoice["number"], invoice["amountCents"])

asyncio.run(main())
```

Both print:

```text
8 vendors
INV-8301 482500
INV-8302 61875
...
client closed
```

Here is what each shape did in that run:

- **The wrapper or decorator** is like an assistant who places your call and, if the line is busy, dials once more. The first `/vendors` call got a `503`. That raises no error by itself, as [14.6](json-and-rest-apis.md#what-should-the-client-do-for-each-status-code) said, so the SDK makes one: `raise_for_status()` in Python, and a check of the status inside `#get` in TypeScript. Python's `@retry_once` decorator and TypeScript's `withRetry` wrapper each caught that error and called once more. Node cannot run TypeScript decorators, so TypeScript code for Node wraps functions by hand.
- **The context manager** is like a door on a spring, which closes behind you however you leave. Python's `async with` and TypeScript's `await using` close the client when the code around them ends.
- **The generator** is like a waiter who brings one dish, and goes back to the kitchen only when you have finished it. `yield` hands one invoice to the loop, and `yield*` does that for each invoice in a list. The generator pauses there until the loop asks for another. It fetches the next page only when the current one is used up.

### What does it check for me, and what does it leave to me?

An SDK sits at your edge, because it is someone else's code between your program and the service. So ask it what [14.4](data-models-and-schemas.md) to [14.7](async-and-await.md) taught you to ask of your own client. Ana asks the agent about this SDK: "Does it check the shape of each answer, or hand us whatever arrived? Which status codes does it retry, and how many times? Does every call have a deadline, and does it close what it opens?" Whatever it does not do, your code does at the edge, with a schema on what it returns and a deadline on each call.

### Try it

The file in `14-8` is the whole made-up SDK. Start the sample API, as in [14.6](json-and-rest-apis.md#try-it). Then send: "Please run the file in 14-8 and show me what it prints. Don't explain it yet."

**Investigate.** Open the file in a text editor. Find the generator and the context manager, and say in one line what each does for the caller.

**Modify.** Send: "Please stop the invoice loop after the first invoice, with break. Don't run it yet." How many pages will it fetch? Will `client closed` print? Then restart the API and ask the agent to run it.

**Make.** Ask: "Does this SDK check the shape of each answer? Does every call have a time limit?" Write down what you would ask its authors to change. Then ask the agent to stop the API.

**What you should see.** After your change, `8 vendors`, one invoice, then `client closed`. The generator fetches a page only when the loop asks for more, and the client closes even after `break`.

**The operating rule:** before you read the details, name the shapes in SDK code, and say in one line what each does for the caller. Then find what it checks for you, and do the rest at your edge.

![Figure 14.9. Annotated excerpts from the made-up AP client. In Python: the class, the async context manager methods __aenter__ and __aexit__, the @retry_once decorator, an async method, and async with. In TypeScript: an async generator method, yield*, await using, and a for await loop.](img/annotated-sdk.png)

*Figure 14.9. Name the shapes first.*

**Next.** [Lab 14](lab.md) puts it all together, and gives you the chance to set the rules Marcus's agent set for him. [14.9](both-ai-vendors.md) shows where your agent differs from the others.
