This guide gets you from zero to a working cross-language call. You will write a math provider in TypeScript and call it from Python.
Node.js 18+, Python 3.11+, and the Saikuro runtime running locally (or use in-memory transport for same-process demos). See Installation.
Install adapters
TypeScriptPythonnpm install @nisoku/saikuropip install saikuroWrite a TypeScript provider
// provider.ts import { SaikuroProvider } from "@nisoku/saikuro"; const provider = new SaikuroProvider("math"); provider.register("add", (a: number, b: number) => a + b); provider.register("multiply", (a: number, b: number) => a * b); await provider.serve("unix:///tmp/saikuro.sock");Run it:
npx tsx provider.tsCall it from Python
# caller.py import asyncio from saikuro import SaikuroClient async def main(): async with SaikuroClient.connect("unix:///tmp/saikuro.sock") as client: result = await client.call("math.add", [10, 32]) print(f"10 + 32 = {result}") # 10 + 32 = 42 result = await client.call("math.multiply", [6, 7]) print(f"6 * 7 = {result}") # 6 * 7 = 42 asyncio.run(main())python caller.pyYou just called TypeScript from Python. No HTTP server, no serialization glue, no shared interface file.
Go the other direction
Same provider in Python:
# provider.py import asyncio from saikuro import SaikuroProvider provider = SaikuroProvider("math") @provider.register("add") def add(a: int, b: int) -> int: return a + b @provider.register("multiply") def multiply(a: int, b: int) -> int: return a * b asyncio.run(provider.serve("unix:///tmp/saikuro.sock"))Call it from TypeScript:
import { SaikuroClient } from "@nisoku/saikuro"; const client = await SaikuroClient.connect("unix:///tmp/saikuro.sock"); const result = await client.call("math.add", [10, 32]); console.log(`10 + 32 = ${result}`);The caller does not know or care which language is on the other end.
What just happened
When your provider called serve(), it:
- Connected to the Saikuro runtime over the transport
- Announced its namespace and function signatures
- Started handling incoming invocations
When your caller ran client.call("math.add", [10, 32]):
- The adapter serialized arguments to MessagePack
- Sent an envelope to the runtime
- The runtime validated and routed it to the
mathprovider - The provider deserialized, ran your function, and sent back the result
- The adapter returned the deserialized result to your code
All of that is invisible. From your code’s perspective you just called a function.