Type mapping
This reference describes canonical, schema-directed application JSON, as used by the schema JSON codecs and MCP structured values. The schema determines the meaning of each value; these shapes are not JavaScript’s JSON.stringify rules for guest objects.
The raw invocation REST API uses a different representation: kind/value
tagged values, positional record fields, and a schema graph. CLI input
literals use native syntax, not these JSON examples. CLI
--format json output is a structured result envelope that includes the typed result, not a bare
canonical value. Custom HTTP APIs can also use raw text or
binary bodies; not every HTTP endpoint uses canonical JSON.
For a record with one u32 field named count:
| Canonical application JSON | REST wire value (schema supplied separately) |
|---|---|
{"count":5} | {"kind":"record","value":{"fields":[{"kind":"u32","value":5}]}} |
Shared JSON rules
- Strings, booleans, and integers up to 32 bits use ordinary JSON strings, booleans, and numbers. A character is a string containing one Unicode scalar value.
- Every
s64andu64value is a decimal string, including elements of lists and typed arrays:"-9223372036854775808","18446744073709551615". This avoids JavaScript precision loss. - Floating-point values use JSON numbers. Non-finite values (
NaNand infinities) are rejected. - Records use schema field names. Optional fields appear as
nullwhen absent on output; input may omit an optional record field. - Options use the inner value or
null. Results use{"ok":value}or{"err":value}. A result arm with no schema payload uses{"ok":null}or{"err":null}. This is distinct from a payload whose schema is an empty tuple or record; consult the SDK’s result schema rather than assuming that every language unit type becomes a payloadless arm. - A variant case with a payload uses a single-key object, such as
{"Text":"hi"}. A payloadless case is a string, such as"Empty", even in a variant that also has payload cases. All-unit enums likewise use strings. Names and casing come from the schema. - Tuples use fixed-order arrays, lists use arrays, maps use arrays of key-value pairs, and flags use arrays of selected names.
Composite examples
Record:
{ "a": "hello", "b": 1234 }Tuple:
[1234, "hello world", 103]List:
["one", "two", "three"]Map (not an object, even with string keys):
[["key1", 100], ["key2", -2]]Flags:
["get", "put"]Rich values
These schema categories have the same canonical shapes across SDKs. Restrictions narrow accepted values; they do not change these shapes.
| Schema category | Canonical JSON example |
|---|---|
| UUID | "550e8400-e29b-41d4-a716-446655440000" |
| Datetime | "2026-01-02T03:04:05.000000000Z" (UTC RFC 3339) |
| Duration | {"nanoseconds":"1500000000"} |
| Text | {"text":"hello","language":"en"} |
| Binary | {"bytes":"AQID","mimeType":"application/octet-stream"} |
| URL | "https://example.com/file" |
| Path | "/data/file.txt" |
| Quantity | {"mantissa":"12345","scale":2,"unit":"m"} |
Binary bytes use unpadded base64url, not a numeric array. Language and MIME type annotations are optional. Ordinary byte lists remain numeric arrays; choose a rich binary schema explicitly when that is the intended contract.
SDK types to JSON
TypeScript
TypeScript agents declare runtime schemas. An arbitrary interface, type alias, or TypeScript union does not define the transport schema. The table uses the Zod adapter as a concrete example. Valibot, ArkType, and Effect adapters have their own supported constructors; do not assume that every schema library feature is supported.
| Schema | Canonical JSON | Guest value / remarks |
|---|---|---|
z.string(), z.boolean() | "hi", true | String, boolean |
z.number() | 1.5 | number, defaults to f64 |
z.bigint() | "-42" | bigint, defaults to signed s64; use s.u64() above 2^63 - 1 |
s.s64(), s.u64() | "-42", "42" | Explicit signed/unsigned 64-bit integers |
s.s8() … s.s32(), s.u8() … s.u32(), s.f32() | 42 | Exact numeric widths |
z.object({count: z.number()}) | {"count":5} | Record |
z.array(z.string()) | ["hi"] | Array |
z.tuple([z.string(), z.number()]) | ["hi",5] | Tuple |
z.enum(["low", "high"]) | "low" | String enum |
z.union([z.string(), z.number()]) | {"case0":"hi"} or {"case1":5} | Generic zero-based case names; no type-alias prefix |
z.discriminatedUnion("tag", [...]) | {"case0":{"tag":"text","val":"hi"}} | Ordinary branch records; no automatic tag/val unwrapping |
z.optional(T), s.option(T) | inner value or null | Missing value is guest undefined |
z.nullable(T) | inner value or null | Missing value is guest null |
z.record(K,V), z.map(K,V) | [["key",5]] | Map schema; guest object or Map respectively |
s.result(Ok,Err) | {"ok":5} or {"err":"failed"} | Typed result |
z.void() as method return | no result | Not an ordinary JSON null value |
Use s.uint8Array(), s.int8Array(), s.uint16Array(), s.int16Array(), s.uint32Array(), s.int32Array(), s.float32Array(), s.float64Array(), s.bigInt64Array(), and s.bigUint64Array() for typed arrays. All produce lists. The bigint constructors produce decimal-string elements, for example ["-1","0","1"]. s.bytes() aliases the list<u8> constructor, not rich binary.
| Rich schema / export | Category |
|---|---|
s.char(), s.uuid() | Character, UUID |
s.datetime(), s.duration() | Datetime, duration |
s.url(), s.text(), s.binary() | URL, text, binary |
Path(options?), Quantity(options) | Path, quantity schema constructors |
import { method, s } from "@golemcloud/golem-ts-sdk"
import { z } from "zod"
const increment = method({
input: { count: s.u32(), note: z.optional(z.string()) },
returns: s.u64(),
})The SDK and generated TypeScript bridges, external and guest, use these types:
| Schema type | SDK and bridge type | Notes |
|---|---|---|
datetime | Datetime, a { seconds: bigint, nanoseconds: number } object | Seconds since the Unix epoch in UTC |
duration | bigint | Signed nanoseconds in the s64 range |
url | string | The URL text as is |
path | string | A path in the agent’s file system |
The SDK exports Datetime from @golemcloud/golem-ts-sdk. External bridges get the same type and the same helpers from @golemcloud/golem-ts-bridge:
Datetime.parse(text)accepts RFC 3339 with any offset and converts the value to UTC.Datetime.toISOString(value)emits UTC RFC 3339 with aZsuffix and up to nine fraction digits.Datetime.fromDate(date)andDatetime.toDate(value)convert from and to a JavaScriptDate.toDatedrops the precision below a millisecond.
parse and toISOString reject a year outside 0000..9999. An external bridge sends a datetime as text, so a datetime outside that range is an encode error.
Unstructured text and binary
Unstructured values carry inline content or a URL reference. Inline content uses the rich text or binary shape, not a bare string or numeric byte list:
| Value | Canonical JSON |
|---|---|
| Inline text | {"inline":{"text":"hi","language":"en"}} |
| Inline binary | {"inline":{"bytes":"AQID","mimeType":"application/octet-stream"}} |
| URL reference (either kind) | {"url":"https://example.com/file"} |
Language and MIME restrictions constrain supported content, not the JSON representation. Guest values use SDK-specific constructors or tags; they are not copies of the canonical JSON objects.
TypeScript
import { method, s } from "@golemcloud/golem-ts-sdk"
import { z } from "zod"
const consume = method({
input: {
text: s.unstructuredText({ languages: ["en"] }),
data: s.unstructuredBinary({ mimeTypes: ["application/octet-stream"] }),
},
returns: z.void(),
})Guest text is {tag:'inline', val:'hi', languageCode:'en'} or {tag:'url', val:'https://example.com/file'}. Guest binary uses val: Uint8Array and optional mimeType. Omit restrictions to accept any language/MIME type.
Multimodal values
A multimodal value is an ordered list of named variant cases. Each occurrence carries one of the declared payload types. Cases, casing, and guest wrappers depend on the SDK; there is no single set of wrapper types shared by every language.
TypeScript
import { method, s } from "@golemcloud/golem-ts-sdk"
import { z } from "zod"
const analyze = method({
input: {
content: s.multimodal([
{ name: "text", schema: s.unstructuredText() },
{ name: "image", schema: s.unstructuredBinary() },
]),
},
returns: z.void(),
})Guest arrays use an outer {tag, value} wrapper, for example [{tag:'text', value:{tag:'inline', val:'hi'}}]. Canonical JSON instead uses [{"text":{"inline":{"text":"hi"}}}]. Use runtime schemas to declare custom cases too.
Values outside this JSON reference
Secrets, quota tokens, and permission cards are host-managed capabilities, not user-constructible JSON values. Streams and futures do not have a canonical JSON transport on this page. Use the dedicated secrets, quotas, and durable streams guides for the supported APIs and boundaries.