Skip to content

Types

Agency's type system is similar to TypeScript's. It includes user-defined generic type aliases and the built-in Record<K, V> / Array<T> / Schema<T> generics, but not all of TypeScript's more advanced features (conditional types, mapped types, etc.).

Agency types are cool because Agency generates Zod Schemas from the types automatically. This means you can use types to specify the structured output format for LLM calls.

ts
let greet: number = llm("add 4 + 5")

This will tell the LLM to respond with a number.

Here are some supported types:

Primitive types:

  • string
  • number
  • boolean
  • null
  • undefined (treated as null)
  • regex (matches a RegExp literal — note that LLMs can't return regex values through structured output, so regex cannot appear in an llm() return type)

Union types. Example:

ts
let status: "success" | "error" = llm("Respond with either 'success' or 'error'")

Array types. Example:

ts
let items: string[] = llm("List 5 fruits")

Object types. Example:

ts
let user: {name: string, age: number} = llm("Provide a user object with name and age")

You can define a new type:

ts
type User = {
  name: string;
  age: number;
}

You can describe a property on an object for the LLM:

ts
type User = {
  name: string # The name of the user
  age: number # The age of the user
}

Records

Use Record types when the set of keys isn't known up front, but you know generally what type all the keys and values need to be. Here is what a record type looks like.

ts
// all keys are strings, all values are either "approve" or "reject"
let votes: Record<string, "approve" | "reject"> = {}
votes["alice"] = "approve"

Note that in agency, because every type needs to be compilable to a schema, the keys can only be numbers, strings, or unions of numbers or strings (or type aliases that are numbers, strings, etc).

Another example:

ts
type Status = "active" | "inactive"

let counts: Record<Status, number> = {
  active: 5,
  inactive: 2
}

Note that if you use a union type for the keys, then all of the keys need to be defined in the object. For the example above, this will cause a type error:

ts
// missing the 'inactive' key
let counts: Record<Status, number> = {
  active: 5
}

Generics

Types can take type parameters:

ts
type Container<T> = { value: T }
type Pair<A, B> = { first: A, second: B }

let c: Container<number> = { value: 42 }
let p: Pair<string, number> = { first: "age", second: 30 }

A type parameter can have a default. When all parameters have defaults, the alias can be used bare:

ts
type StringMap<V = any> = Record<string, V>

let untyped: StringMap = {}                       // V defaults to any
let typed:   StringMap<number> = { count: 1 }     // V explicit

Default parameters must come after all required ones, mirroring TypeScript:

ts
type Pair<A, B = string> = { first: A, second: B }  // ok
type Pair<A = string, B> = { first: A, second: B }  // error

Recursive generic aliases work:

ts
type Tree<T> = { value: T, children: Tree<T>[] }

Array<T> and Schema<T> are built-in generics.

Array<T> is equivalent to T[], and Schema<T> is equivalent to the type of schema(T). Schemas come later in the guide, but click here if you want to learn more.

Optional properties

Add ? after a property name to make it optional:

ts
type Options = {
  model?: string
  temperature?: number
}

This is shorthand for key: T | undefined — when the property is missing from a value, that's still type-correct. For example:

ts
def configure(opts: Options): void { ... }
configure({ model: "gpt-4" })  // ok — temperature omitted
configure({})                  // ok — both omitted

Excess properties

When you write an object literal, every key in the literal must be in its declared type. This helps catch typos like modle: instead of model::

ts
const cfg: Options = { modle: "gpt-4" }  // error: Unknown property 'modle'

This check fires only on object literals at the argument site. Assigning the same object to a variable first skips the excess property check, matching the behavior of TypeScript.

ts
const badCfg = { modle: "gpt-4" }
const cfg: Options = badCfg  // ok — excess property check doesn't apply here

Suppressing typecheck errors

How to opt out of typechecking:

// @tc-nocheck — silences every typecheck error in the file. Must appear at the top of the file:

ts
// @tc-nocheck

// the rest of this file is not typechecked
def foo(x: number) {
  print(x + "oops")
}

// @tc-ignore — silences typecheck errors on the next line only. Must be on its own line, Agency does not allow trailing comments.

ts
def take(x: number): void { print(x) }

node main() {
  // @tc-ignore
  take("not a number")
  take(42)
}

See also