Idun SDK
Thin, stdlib-only client + CLI for the NatureLM-Idun-5-MoE agent on Azure AI Foundry — plus a 13-provider registry, a 16-bit retro console, and an MCP server that exposes the whole registry.
Runs headless on Termux/Android with nothing but the Python standard library
(no httpx, no azure-identity, no Flask). Idun is a tool agent (reasons +
calls tools like web_search); this SDK surfaces the full agent trajectory
(reasoning + tool calls) instead of a black-box answer.
What's in the box (v1.0)
- 13-provider registry over 3 transports (OpenAI-compatible, Anthropic, Hugging Face). Any OpenAI-compatible endpoint works with zero code changes.
idun-multi— a 16-colour ANSI "16-bit" console with provider switching, credential wizard, model discovery, fallback chains, themes, and a REPL.- Config file
~/.idun/config.tomlas the primary config source (env vars still win; registry defaults are the fallback). - Structured output —
--jsonon any command and aschemacommand. - Fallback chains — fan a prompt across providers until one answers
(
IDUN_CHAIN). - Theme system —
classic/c64/gameboy/amiga/cga. - Live model discovery —
GET {base}/models, cached 24h. - MCP server parity —
idun_providers,idun_ask,idun_racealongside the originalidun_chat/idun_trace/idun_export/idun_tokentools. - Async client —
AsyncIdunClientfor concurrent fan-out via asyncio. - Verified install —
test.shbuilds a fresh wheel, installs it into a temp dir, runs the offline suite, and asserts the console scripts resolve to this package. - CI — Python 3.8–3.14 matrix + a native Termux/aarch64 job + ruff lint.
Quick start
pip install idun-sdk # or: ./install.sh (editable, no-deps)
./test.sh # post-install verification (offline, isolated)
idun-multi doctor # env + credential + console-script audit
idun-multi providers # all 13 providers + credential state
idun-multi login --provider groq # hidden prompt -> ~/.idun/groq.token (0600)
idun-multi ask "explain MoE routing" # active provider
idun-multi -p openrouter ask "hi" # one-off provider
idun-multi race "Name one planet." # fan one prompt at every ready provider
idun-multi models --discover # live model list for the active provider
idun-multi theme c64 # switch retro palette
idun-multi doctor # env + credential + console-script audit
| Provider | Transport | Credential | Tier |
|---|---|---|---|
azure |
Foundry responses | Entra device-code (idun login) |
paid |
openai |
openai | OPENAI_API_KEY |
paid |
anthropic |
anthropic messages | ANTHROPIC_API_KEY |
paid |
groq |
openai | GROQ_API_KEY |
free tier |
openrouter |
openai | OPENROUTER_API_KEY |
free tier |
together |
openai | TOGETHER_API_KEY |
paid |
deepseek |
openai | DEEPSEEK_API_KEY |
paid |
mistral |
openai | MISTRAL_API_KEY |
paid |
gemini |
openai | GEMINI_API_KEY |
free tier |
xai |
openai | XAI_API_KEY |
paid |
hf |
hf inference | HF_TOKEN (optional) |
free |
ollama |
openai | none (local) | free |
local |
openai | none | free |
Any OpenAI-compatible endpoint works without code changes — override per
provider with IDUN_<ID>_BASE and IDUN_<ID>_MODEL:
export IDUN_LOCAL_BASE=http://127.0.0.1:8080/v1 # llama.cpp / vLLM / LiteLLM
idun-multi -p local ask "hello"
Azure Foundry: bring your own resource
No tenant is bundled with this package. The azure provider is configured
entirely from the environment, and IdunClient() raises a clear error rather
than pointing you at somebody else's resource:
export IDUN_BASE=https://<your-resource>.services.ai.azure.com
export IDUN_PROJECT=<your-project>
export IDUN_AGENT=<your-agent> # optional, default NatureLM-Idun-5-MoE
export IDUN_TENANT=<your-tenant-guid> # optional, default "organizations"
idun login # Entra device-code
Persist them in ~/.idunrc (mode 0600, outside the repo) and
source ~/.idunrc. Every other provider works with no Azure config at all.
Python API:
from idun.providers import complete, list_providers
c = complete("groq", "Summarise MoE routing in one line.")
print(c.text, c.model, c.latency_ms, c.total_tokens)
Credentials resolve from the provider env keys first, then
~/.idun/<id>.token (file 0600, dir 0700). Keys are never echoed and never
passed via argv.
Configuration file
~/.idun/config.toml is the primary config source. Resolution order is
env var > ~/.idun/config.toml > registry default, so whatever you put in
the file is always overridable from the shell without editing it.
[defaults]
provider = "groq"
theme = "c64"
[providers.groq]
model = "llama-3.3-70b-versatile"
base = "https://api.groq.com/openai/v1"
[providers.openai]
model = "gpt-4o-mini"
The file is parsed by a stdlib-only TOML reader (no tomllib requirement, so
it works on Python 3.8+). A corrupt or malformed file never crashes startup —
you get a clear warning and the registry defaults are used instead.
Structured output
Every command that produces a completion supports --json, which emits a
single completion-shaped JSON object at the end (streaming still prints
chunks live in non-JSON mode). Use the schema command to inspect the
response schema per provider, including whether json_mode is supported:
idun-multi ask "What is a MoE?" --json
idun-multi schema groq
{"provider": "groq", "model": "llama-3.3-70b-versatile",
"text": "...", "prompt_tokens": 7, "completion_tokens": 23,
"total_tokens": 30, "latency_ms": 412, "raw": {...}}
Fallback chains
IDUN_CHAIN lists providers to try in order; the first one that returns a
non-retryable answer wins, and the chosen link is recorded in raw._served_by:
export IDUN_CHAIN=groq,openrouter,openai
idun-multi ask "Ansible vs Terraform, one line." # tries groq, then openrouter, then openai
Skipped links on 429 / 5xx / auth / transport errors are reported in
raw._chain. Programmatic:
from idun.providers import complete_chain
c = complete_chain(["groq", "openrouter"], "hi")
print(c.raw["_served_by"])
Themes
The 16-colour chrome is selectable via IDUN_THEME (read at import) or the
theme command. Palettes: classic (default), c64, gameboy, amiga,
cga. Unknown ids fall back to classic.
idun-multi theme gameboy
NO_COLOR=1 / IDUN_NO_RETRO=1 disable colour entirely, IDUN_ASCII=1
swaps box-drawing for pure ASCII, IDUN_FORCE_COLOR=1 keeps colour through a
pipe, and IDUN_NO_TYPEWRITER=1 prints answers instantly.
Model discovery
models shows the registry model list; --discover fetches the live
GET {base}/models endpoint and caches it under ~/.idun/models/<id>.json
for 24h (override with IDUN_MODELS_CACHE_MAX_AGE, disable with
IDUN_NO_MODELS_CACHE). On any error or for non-OpenAI transports, the
registry list is returned so callers always get something usable.
idun-multi models groq --discover
MCP server
idun_mcp.py is a zero-dependency stdio MCP server (no FastMCP / httpx):
python3 idun_mcp.py
Tools exposed:
| Tool | Purpose |
|---|---|
idun_chat |
final answer from the Idun agent |
idun_trace |
full trajectory (steps + text) |
idun_export |
trajectory as JSON / Markdown |
idun_token |
inspect stored token state (no secret leak) |
idun_providers |
list the registry + credential state |
idun_ask |
send one prompt to ANY provider in the registry |
idun_race |
fan one prompt at several providers (latency + state) |
Add to any MCP client:
{ "mcpServers": { "idun": { "command": "python3", "args": ["/abs/path/idun-sdk/idun_mcp.py"] } } }
Async client
import asyncio
from idun.async_client import AsyncIdunClient
async def main():
c = AsyncIdunClient()
results = await c.gather(
c.acomplete("groq", "hi"),
c.acomplete("openai", "hi"),
)
for r in results:
print(r.text)
asyncio.run(main())
AsyncIdunClient runs each (blocking) stdlib HTTP call in a worker thread via
asyncio.to_thread — no dedicated thread pool — so the event loop stays free
for concurrent fan-out.
Security
- Secrets are never on the command line.
idun-multi loginreads keys withgetpass(hidden input); they are written to~/.idun/<id>.token(mode 0600, inside a 0700 dir), never echoed, never logged. - Error bodies are sanitised. Upstream error responses are truncated and scrubbed before they reach stderr/logs, so a token accidentally echoed by a provider is never relayed back.
- SSRF guard. Every outbound request is validated to be
http:///https://via_require_http_urlbefore any connection is made —file://,gopher://, and similar schemes are rejected, so a maliciousIDUN_*_BASEcannot read local files. - No bundled tenant/resource. The Azure provider is configured purely from your environment; the package ships no credentials and no default resource.
- Token inspection is secret-free.
idun token/ theidun_tokenMCP tool report validity, expiry, and the account UPN — never the bearer token itself.
Azure request shape (verified)
POST {base}/api/projects/{project}/agents/{agent}/endpoint/protocols/openai/responses?api-version=2025-05-15-preview
Authorization: Bearer ***
{"model": "model-router", "input": "<prompt>", "max_output_tokens": 4096}
modelMUST be"model-router"(agent id is in the URL).- Do not send a
toolskey — the agent owns its tools (else400 invalid_payload).
Install & verify
pip install idun-sdk # or: ./install.sh
./test.sh # builds a wheel, installs into a temp dir,
# runs the offline suite, asserts console
# scripts resolve to THIS package
Requires Python ≥ 3.8; no third-party dependencies (stdlib-only, runs headless on Termux/Android).
Links
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file idun_sdk-1.0.0.tar.gz.
File metadata
- Download URL: idun_sdk-1.0.0.tar.gz
- Upload date:
- Size: 98.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adb469cec6169ae707fcda67157a7343f6dff7568a2cb34d36bc7329196f1450
|
|
| MD5 |
708c7188f310d2fa83dab076d456897e
|
|
| BLAKE2b-256 |
15f8bdf83b77a970edb632d68f4daaf7210c88b331303429bc3bc7f8e21f4002
|
File details
Details for the file idun_sdk-1.0.0-py3-none-any.whl.
File metadata
- Download URL: idun_sdk-1.0.0-py3-none-any.whl
- Upload date:
- Size: 77.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
102e46a8882d8ddf81c09a7ef14ef794a6a60d86b1c0779fe6a642a8278755fa
|
|
| MD5 |
751f9567b042106d17bc694125c0b4a0
|
|
| BLAKE2b-256 |
6ab16d4098a374afd0fe6bdf094cfdf1119491c2be1abf4662e00c0527732273
|