# `Nous.CodeMode.Sdk`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/code_mode/sdk.ex#L1)

Renders the typed SDK a Code Mode program is written against.

Under `mode: :code` this text is the model's **only** declaration of the
tools — there is no native tool schema alongside it — which is what drives
every rule below.

## Byte stability

Tools are sorted lexicographically by name and every map is walked in a
sorted order, so the same tool set renders to the same bytes on every call.
The SDK travels in the `run_code` tool's description, i.e. in the prompt
prefix of every request; an unstable ordering would invalidate the provider's
KV cache each turn and silently double the cost of a run. Assert
`render(lang, tools) == render(lang, Enum.shuffle(tools))` when you change
anything here.

## Total, never raising, on schemas

`json_schema_to_type/2` is total over arbitrary input. Any node it cannot
express — a `$ref` it cannot resolve, an unknown `type`, a malformed map, a
nesting deeper than a dozen levels — becomes the language's escape hatch
(`unknown` for JS/TS, `Any` for Python). An exotic schema costs that node its
type; it must never cost the SDK its parseability, because a program written
against an unparseable SDK cannot call any tool at all.

## Addressing

`tools` is an object/mapping indexed by each tool's **exact** name, so
`tools["my-tool"](args)` works with no alias table and no name mangling.
Names are emitted as JSON string literals, which are valid string literals in
both target languages.

## Languages

`:javascript` and `:typescript` render the same TypeScript declaration —
under JavaScript it is read as documentation, not executed. `:python` renders
a stub module. Structural fidelity differs by language and that is deliberate:
Python has no intersection type and no anonymous nested record type, so
`allOf` and nested objects degrade there while JS/TS keeps them.

# `language`

```elixir
@type language() :: :javascript | :typescript | :python
```

A language this generator can render. Providers report their language as a
string via `c:Nous.CodeRuntime.language/1`; both forms are accepted.

# `coerce_language`

```elixir
@spec coerce_language(term()) :: {:ok, language()} | :error
```

Resolve a language given as an atom or a string.

Returns `:error` rather than raising, so a caller holding a language it read
from provider configuration can fall back instead of failing a run.

## Examples

    iex> Nous.CodeMode.Sdk.coerce_language("typescript")
    {:ok, :typescript}

    iex> Nous.CodeMode.Sdk.coerce_language(:ruby)
    :error

# `error_class`

```elixir
@spec error_class() :: String.t()
```

The exception class name the SDK tells the program to expect from a failed
call. Bindings carry the same name in `Nous.CodeRuntime.Binding.error_class`.

# `escape_hatch`

```elixir
@spec escape_hatch(language() | String.t()) :: String.t()
```

The type a node gets when the generator cannot express it.

## Examples

    iex> Nous.CodeMode.Sdk.escape_hatch(:typescript)
    "unknown"

    iex> Nous.CodeMode.Sdk.escape_hatch(:python)
    "Any"

# `global`

```elixir
@spec global() :: String.t()
```

The name of the global the program calls tools through.

The bindings a provider is handed must use this same global, or the SDK
describes something the program cannot reach.

# `json_schema_to_type`

```elixir
@spec json_schema_to_type(language() | String.t(), term()) :: String.t()
```

Convert one JSON Schema node to a `language` type expression.

Total: any node the generator cannot express returns `escape_hatch/1` rather
than raising. Accepts string- or atom-keyed schema maps.

## Examples

    iex> Nous.CodeMode.Sdk.json_schema_to_type(:typescript, %{"type" => "integer"})
    "number"

    iex> Nous.CodeMode.Sdk.json_schema_to_type(:python, %{"type" => "array"})
    "list[Any]"

    iex> Nous.CodeMode.Sdk.json_schema_to_type(:typescript, %{"$ref" => "#/definitions/x"})
    "unknown"

# `languages`

```elixir
@spec languages() :: [language()]
```

Every language `render/2` can generate.

# `render`

```elixir
@spec render(language() | String.t(), [Nous.Tool.t()]) :: String.t()
```

Render the SDK declaring `tools`.

Tools are deduplicated by name (first occurrence wins, matching the runner's
own lookup) and sorted lexicographically, so output depends on the tool
*set*, never on the order it arrived in.

Raises `ArgumentError` for a language this generator does not know; that is
provider configuration, not model input.

## Examples

    iex> tool = %Nous.Tool{
    ...>   name: "greet",
    ...>   description: "Say hi",
    ...>   function: fn _ctx, _args -> {:ok, "hi"} end,
    ...>   parameters: %{
    ...>     "type" => "object",
    ...>     "properties" => %{"name" => %{"type" => "string"}},
    ...>     "required" => ["name"]
    ...>   }
    ...> }
    iex> sdk = Nous.CodeMode.Sdk.render("javascript", [tool])
    iex> sdk =~ ~s|"greet"(args: {|
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
