mu-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
- package/dist/bundle/chunks/anthropic.js +75 -0
- package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +83 -0
- package/dist/bundle/chunks/chunk-4L3WN2XY.js +2 -0
- package/dist/bundle/chunks/chunk-6CYCI3EJ.js +12 -0
- package/dist/bundle/chunks/chunk-6R7UI4IY.js +2 -0
- package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
- package/dist/bundle/chunks/chunk-DRQDKNXA.js +2 -0
- package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
- package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
- package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
- package/dist/bundle/chunks/chunk-NUHFSC37.js +25 -0
- package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
- package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
- package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
- package/dist/bundle/chunks/chunk-QB4HYZZE.js +1561 -0
- package/dist/bundle/chunks/chunk-RCZIEVGO.js +11 -0
- package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
- package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
- package/dist/bundle/chunks/chunk-TRDNDS6A.js +4 -0
- package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
- package/dist/bundle/chunks/chunk-WIUZI2CJ.js +11 -0
- package/dist/bundle/chunks/chunk-XDUWOHPL.js +6 -0
- package/dist/bundle/chunks/chunk-XNGRGP62.js +2 -0
- package/dist/bundle/chunks/chunk-Z2DUOAN4.js +2 -0
- package/dist/bundle/chunks/github-copilot.js +2 -0
- package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
- package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
- package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +13 -0
- package/dist/bundle/chunks/image-resize-worker.js +2 -0
- package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
- package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
- package/dist/bundle/chunks/kimi-coding.js +2 -0
- package/dist/bundle/chunks/lib-HGQMNYVT.js +9 -0
- package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
- package/dist/bundle/chunks/multipart-parser-SQ6GDJL7.js +3 -0
- package/dist/bundle/chunks/node-O3RIBJRH.js +16 -0
- package/dist/bundle/chunks/node-TWVVAGPO.js +2 -0
- package/dist/bundle/chunks/openai-codex-responses-QDSOTBBO.js +10 -0
- package/dist/bundle/chunks/openai-codex.js +75 -0
- package/dist/bundle/chunks/openai-completions-KQPOPEVM.js +7 -0
- package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +2 -0
- package/dist/bundle/chunks/openrouter-images-N4XVVFSD.js +2 -0
- package/dist/bundle/chunks/openrouter.js +75 -0
- package/dist/bundle/chunks/pi-messages-GFKBWJHZ.js +8 -0
- package/dist/bundle/chunks/radius.js +75 -0
- package/dist/bundle/chunks/src-UK77SYZE.js +4 -0
- package/dist/bundle/chunks/virtual-modules-ROULVYS3.js +2 -0
- package/dist/bundle/chunks/xai.js +2 -0
- package/dist/bundle/cli.js +3 -0
- package/dist/bundle/index.js +2 -0
- package/dist/bundle/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/dist/bundle/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/dist/bundle/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/dist/bundle/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/dist/bundle/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/dist/bundle/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/dist/bundle/rpc-entry.js +3 -0
- package/dist/core/export-html/template.css +1066 -0
- package/dist/core/export-html/template.html +55 -0
- package/dist/core/export-html/template.js +1864 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +78 -0
- package/dist/modes/interactive/assets/clankolas.png +0 -0
- package/dist/modes/interactive/theme/dark.json +91 -0
- package/dist/modes/interactive/theme/light.json +90 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +41 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-controller.js +130 -0
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -0
- package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
- package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-json.js +130 -0
- package/dist/modes/interactive/theme/theme-json.js.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +356 -0
- package/dist/modes/interactive/theme/theme.d.ts +131 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme.js +991 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -0
- package/dist/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/dist/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/dist/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/dist/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/dist/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/dist/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/docs/compaction.md +444 -0
- package/docs/containerization.md +156 -0
- package/docs/custom-provider.md +786 -0
- package/docs/development.md +90 -0
- package/docs/docs.json +160 -0
- package/docs/environment-variables.md +100 -0
- package/docs/extensions.md +3037 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/index.md +84 -0
- package/docs/json.md +98 -0
- package/docs/keybindings.md +239 -0
- package/docs/llama-cpp.md +101 -0
- package/docs/models.md +605 -0
- package/docs/packages.md +228 -0
- package/docs/prompt-templates.md +96 -0
- package/docs/providers.md +317 -0
- package/docs/quickstart.md +167 -0
- package/docs/rpc.md +1618 -0
- package/docs/sdk.md +1226 -0
- package/docs/security.md +59 -0
- package/docs/session-format.md +480 -0
- package/docs/sessions.md +172 -0
- package/docs/settings.md +428 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +232 -0
- package/docs/terminal-setup.md +192 -0
- package/docs/termux.md +126 -0
- package/docs/themes.md +322 -0
- package/docs/tmux.md +63 -0
- package/docs/tui.md +961 -0
- package/docs/usage.md +312 -0
- package/docs/windows.md +39 -0
- package/examples/README.md +28 -0
- package/examples/extensions/README.md +212 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bash-spawn-hook.ts +30 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/border-status-editor.ts +150 -0
- package/examples/extensions/built-in-tool-renderer.ts +249 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/commands.ts +72 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +117 -0
- package/examples/extensions/custom-footer.ts +64 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +405 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
- package/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/examples/extensions/dynamic-resources/dynamic.json +81 -0
- package/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/examples/extensions/dynamic-resources/index.ts +15 -0
- package/examples/extensions/dynamic-tools.ts +74 -0
- package/examples/extensions/entry-renderer.ts +41 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/examples/extensions/gondolin/index.ts +531 -0
- package/examples/extensions/gondolin/package-lock.json +185 -0
- package/examples/extensions/gondolin/package.json +19 -0
- package/examples/extensions/handoff.ts +190 -0
- package/examples/extensions/hello.ts +26 -0
- package/examples/extensions/hidden-thinking-label.ts +53 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/minimal-mode.ts +426 -0
- package/examples/extensions/modal-editor.ts +85 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +57 -0
- package/examples/extensions/overlay-qa-tests.ts +1450 -0
- package/examples/extensions/overlay-test.ts +153 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +66 -0
- package/examples/extensions/plan-mode/index.ts +390 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +436 -0
- package/examples/extensions/project-trust.ts +64 -0
- package/examples/extensions/prompt-customizer.ts +49 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/provider-payload.ts +18 -0
- package/examples/extensions/qna.ts +118 -0
- package/examples/extensions/question.ts +286 -0
- package/examples/extensions/questionnaire.ts +448 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/rpc-demo.ts +118 -0
- package/examples/extensions/sandbox/index.ts +321 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +343 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +32 -0
- package/examples/extensions/structured-output.ts +65 -0
- package/examples/extensions/subagent/README.md +177 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +157 -0
- package/examples/extensions/subagent/index.ts +1038 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +199 -0
- package/examples/extensions/system-prompt-header.ts +17 -0
- package/examples/extensions/tic-tac-toe.ts +1008 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/titlebar-spinner.ts +58 -0
- package/examples/extensions/todo.ts +297 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +146 -0
- package/examples/extensions/trigger-compact.ts +50 -0
- package/examples/extensions/truncated-tool.ts +195 -0
- package/examples/extensions/widget-placement.ts +9 -0
- package/examples/extensions/with-deps/index.ts +32 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/extensions/working-indicator.ts +123 -0
- package/examples/extensions/working-message-test.ts +25 -0
- package/examples/plugins/pi-example-plugin/README.md +38 -0
- package/examples/plugins/pi-example-plugin/package.json +10 -0
- package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
- package/examples/plugins/pi-example-plugin/src/session.ts +23 -0
- package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
- package/examples/rpc-extension-ui.ts +641 -0
- package/examples/sdk/01-minimal.ts +26 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +75 -0
- package/examples/sdk/04-skills.ts +55 -0
- package/examples/sdk/05-tools.ts +48 -0
- package/examples/sdk/06-extensions.ts +99 -0
- package/examples/sdk/07-context-files.ts +47 -0
- package/examples/sdk/08-prompt-templates.ts +51 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
- package/examples/sdk/10-settings.ts +53 -0
- package/examples/sdk/11-sessions.ts +52 -0
- package/examples/sdk/12-full-control.ts +74 -0
- package/examples/sdk/13-session-runtime.ts +67 -0
- package/examples/sdk/README.md +140 -0
- package/judge/THIRD_PARTY_NOTICES.md +35 -0
- package/judge/agents/browser.md +17 -0
- package/judge/agents/investigator.md +16 -0
- package/judge/agents/planner.md +15 -0
- package/judge/agents/reviewer.md +17 -0
- package/judge/agents/scout.md +19 -0
- package/judge/agents/worker.md +15 -0
- package/judge/dist/chrome.js +326 -0
- package/judge/dist/kyrn-judge.js +52542 -0
- package/judge/package.json +6 -0
- package/judge/prompts/implement-and-review.md +14 -0
- package/judge/prompts/implement.md +15 -0
- package/judge/prompts/init.md +18 -0
- package/judge/prompts/scout-and-plan.md +12 -0
- package/judge/skills/mu-browser/SKILL.md +30 -0
- package/judge/skills/mu-github/SKILL.md +90 -0
- package/kyrn/bin/kyrn-doctor +202 -0
- package/kyrn/bin/kyrn-judge-local +131 -0
- package/kyrn/bin/kyrn-ledger +90 -0
- package/kyrn/bin/mu.mjs +1120 -0
- package/kyrn/local-judge/requirements.lock.txt +26 -0
- package/kyrn/local-judge/server.py +332 -0
- package/package.json +45 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
anyio==4.15.1
|
|
2
|
+
attrs==26.1.0
|
|
3
|
+
cattrs==26.2.0
|
|
4
|
+
certifi==2026.7.22
|
|
5
|
+
click==8.5.0
|
|
6
|
+
coremltools==9.0
|
|
7
|
+
filelock==4.0.1
|
|
8
|
+
fsspec==2026.9.0
|
|
9
|
+
h11==0.16.0
|
|
10
|
+
hf-xet==1.6.0
|
|
11
|
+
httpcore==1.0.9
|
|
12
|
+
httpx==0.28.1
|
|
13
|
+
huggingface-hub==1.32.0
|
|
14
|
+
idna==3.20
|
|
15
|
+
laya-coreml==0.1.0
|
|
16
|
+
mpmath==1.3.0
|
|
17
|
+
numpy==2.1.3
|
|
18
|
+
packaging==26.3
|
|
19
|
+
protobuf==7.36.2
|
|
20
|
+
pyaml==26.7.0
|
|
21
|
+
pyyaml==6.0.3
|
|
22
|
+
safetensors==0.8.0
|
|
23
|
+
sympy==1.14.0
|
|
24
|
+
tokenizers==0.23.2
|
|
25
|
+
tqdm==4.70.1
|
|
26
|
+
typing-extensions==4.16.0
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""mu local judge sidecar.
|
|
3
|
+
|
|
4
|
+
Serves a Laya Core ML checkpoint (https://github.com/mizorewww/laya-coreml) over
|
|
5
|
+
the same wire contract as the AI Gateway `evaluation-model` endpoint, so the
|
|
6
|
+
mu kernel can swap Jev for a local model without translating anything:
|
|
7
|
+
|
|
8
|
+
POST /evaluate {"state": ..., "questions": {id: {type, instructions, criteria?}}}
|
|
9
|
+
-> {"answers": {...}, "usage": {...}, "warnings": [...], "providerMetadata": {"laya": {...}}}
|
|
10
|
+
GET /health -> {"status": "ok" | "warming", ...}
|
|
11
|
+
|
|
12
|
+
Question types use the gateway names: `boolean` (Laya calls it `noul`), `choice`, `score`.
|
|
13
|
+
|
|
14
|
+
The server binds to loopback only, never logs state or question text, and keeps
|
|
15
|
+
the model behind one lock because a Core ML model is not safe to call concurrently.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import threading
|
|
23
|
+
import time
|
|
24
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
28
|
+
DEFAULT_PORT = 47823
|
|
29
|
+
MAX_BODY_BYTES = 1024 * 1024
|
|
30
|
+
# Measured on an M3 Max with 10-question preflights: no floor p50 411 ms (one shape switch per request),
|
|
31
|
+
# 256 -> 124 ms, 384 -> 155 ms. Requests longer than the floor still work; they just pay the switch.
|
|
32
|
+
DEFAULT_MIN_LENGTH = 256
|
|
33
|
+
DEFAULT_MODEL_DIR = Path(__file__).resolve().parent / "models" / "laya-multilingual-coreml"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BadRequest(ValueError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def setting(name, default):
|
|
41
|
+
"""`MU_<name>`, or the `KYRN_<name>` spelling from before the rename."""
|
|
42
|
+
return os.environ.get(f"MU_{name}") or os.environ.get(f"KYRN_{name}") or default
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _text(value):
|
|
46
|
+
"""Laya serializes non-string instructions with ASCII escapes, which mangles CJK. Do it here instead."""
|
|
47
|
+
return value if isinstance(value, str) else json.dumps(value, ensure_ascii=False)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def to_laya_question(question_id, question):
|
|
51
|
+
if not isinstance(question, dict):
|
|
52
|
+
raise BadRequest(f'Question "{question_id}" must be an object')
|
|
53
|
+
kind = question.get("type")
|
|
54
|
+
if "instructions" not in question:
|
|
55
|
+
raise BadRequest(f'Question "{question_id}" is missing instructions')
|
|
56
|
+
instructions = _text(question["instructions"])
|
|
57
|
+
criteria = question.get("criteria")
|
|
58
|
+
|
|
59
|
+
if kind == "boolean":
|
|
60
|
+
out = {"type": "noul", "instructions": instructions}
|
|
61
|
+
if isinstance(criteria, dict):
|
|
62
|
+
kept = {key: criteria[key] for key in ("false", "true") if criteria.get(key) not in (None, "")}
|
|
63
|
+
if kept:
|
|
64
|
+
out["criteria"] = kept
|
|
65
|
+
return out
|
|
66
|
+
if kind == "choice":
|
|
67
|
+
if not isinstance(criteria, dict) or not criteria:
|
|
68
|
+
raise BadRequest(f'Choice question "{question_id}" needs a non-empty criteria object')
|
|
69
|
+
return {"type": "choice", "instructions": instructions, "criteria": dict(criteria)}
|
|
70
|
+
if kind == "score":
|
|
71
|
+
if not isinstance(criteria, list) or len(criteria) < 2:
|
|
72
|
+
raise BadRequest(f'Score question "{question_id}" needs at least two levels')
|
|
73
|
+
return {
|
|
74
|
+
"type": "score",
|
|
75
|
+
"instructions": instructions,
|
|
76
|
+
"criteria": ["" if level is None else level for level in criteria],
|
|
77
|
+
}
|
|
78
|
+
raise BadRequest(f'Question "{question_id}" has unknown type {kind!r}; expected boolean, choice or score')
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def from_laya_answer(answer):
|
|
82
|
+
kind = answer["type"]
|
|
83
|
+
if kind == "noul":
|
|
84
|
+
return {"type": "boolean", "probability": answer["noul"]}
|
|
85
|
+
if kind == "choice":
|
|
86
|
+
return {"type": "choice", "choice": answer["choice"], "probabilities": answer["probabilities"]}
|
|
87
|
+
return {"type": "score", "score": answer["score"], "probabilities": answer["probabilities"]}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class LocalJudge:
|
|
91
|
+
def __init__(self, model_dir, compute_units=None, min_length=0):
|
|
92
|
+
import laya_coreml as laya
|
|
93
|
+
|
|
94
|
+
self.model_dir = Path(model_dir)
|
|
95
|
+
self.min_length = min_length
|
|
96
|
+
started = time.perf_counter()
|
|
97
|
+
self.agent = laya.load(str(self.model_dir), local_files_only=True, compute_units=compute_units)
|
|
98
|
+
self.load_seconds = time.perf_counter() - started
|
|
99
|
+
self.lock = threading.Lock()
|
|
100
|
+
self.warm = False
|
|
101
|
+
self.requests = 0
|
|
102
|
+
self.started_at = time.time()
|
|
103
|
+
manifest = getattr(self.agent, "manifest", {})
|
|
104
|
+
self.model_id = "laya:" + str(manifest.get("repository") or self.model_dir.name)
|
|
105
|
+
self.shape = getattr(self.agent, "shape", {})
|
|
106
|
+
|
|
107
|
+
def uniform_shape(self, state, laya_questions):
|
|
108
|
+
"""Pad every question of a request to one input length.
|
|
109
|
+
|
|
110
|
+
Core ML re-specializes the GPU program whenever the enumerated length
|
|
111
|
+
changes (about 350 ms on an M3 Max, against 40 ms for a steady shape),
|
|
112
|
+
and the questions of one request differ in length because their
|
|
113
|
+
options do. One bucket per request removes those switches; the floor
|
|
114
|
+
keeps consecutive requests on the same bucket too.
|
|
115
|
+
"""
|
|
116
|
+
lengths = self.agent.shape.get("lengths")
|
|
117
|
+
if not lengths:
|
|
118
|
+
return self.agent.shape
|
|
119
|
+
items, _internal = self.agent.prepare(state, laya_questions)
|
|
120
|
+
longest = max(max(len(item["ids"]) for item in items), self.min_length)
|
|
121
|
+
target = next((n for n in lengths if n >= longest), lengths[-1])
|
|
122
|
+
return dict(self.agent.shape, lengths=[target])
|
|
123
|
+
|
|
124
|
+
def diagnostics(self, state, laya_questions):
|
|
125
|
+
"""Report what the token budget silently cut. Never raises."""
|
|
126
|
+
warnings = []
|
|
127
|
+
try:
|
|
128
|
+
from laya_coreml.common import build_prefix, render_options, serialize_state
|
|
129
|
+
|
|
130
|
+
tok, cfg = self.agent.tok, self.agent.cfg
|
|
131
|
+
max_len, head_max_len = cfg.get("max_len", 512), cfg.get("head_max_len", 192)
|
|
132
|
+
state_tokens = len(tok(serialize_state(state).replace(tok.mask_token, " "))["input_ids"])
|
|
133
|
+
for question_id, definition in laya_questions.items():
|
|
134
|
+
q = self.agent._to_internal(definition)
|
|
135
|
+
prefix, _markers = build_prefix(tok, q, head_max_len)
|
|
136
|
+
full_head = len(tok("%s question: %s" % (q["t"], q["ins"]))["input_ids"])
|
|
137
|
+
full_options = sum(1 + len(tok(" " + option)["input_ids"]) for option in render_options(q))
|
|
138
|
+
full_prefix = 1 + full_head + 1 + full_options + 1
|
|
139
|
+
if len(prefix) < full_prefix:
|
|
140
|
+
warnings.append(
|
|
141
|
+
{
|
|
142
|
+
"type": "question_truncated",
|
|
143
|
+
"questionId": question_id,
|
|
144
|
+
"message": f"question and options need {full_prefix} tokens, {len(prefix)} were kept",
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
room = max(0, max_len - len(prefix) - 1)
|
|
148
|
+
if state_tokens > room:
|
|
149
|
+
warnings.append(
|
|
150
|
+
{
|
|
151
|
+
"type": "state_truncated",
|
|
152
|
+
"questionId": question_id,
|
|
153
|
+
"message": f"state has {state_tokens} tokens, only the first {room} were read",
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
except Exception as error: # noqa: BLE001 - diagnostics are best effort
|
|
157
|
+
warnings.append({"type": "diagnostics_failed", "message": type(error).__name__})
|
|
158
|
+
return warnings
|
|
159
|
+
|
|
160
|
+
def evaluate(self, state, questions):
|
|
161
|
+
if not isinstance(questions, dict) or not questions:
|
|
162
|
+
raise BadRequest("questions must be a non-empty object keyed by question id")
|
|
163
|
+
if not isinstance(state, (str, dict, list)):
|
|
164
|
+
raise BadRequest("state must be a string, an object or an array")
|
|
165
|
+
laya_questions = {qid: to_laya_question(qid, q) for qid, q in questions.items()}
|
|
166
|
+
|
|
167
|
+
started = time.perf_counter()
|
|
168
|
+
with self.lock:
|
|
169
|
+
waited = time.perf_counter() - started
|
|
170
|
+
original_shape = self.agent.shape
|
|
171
|
+
try:
|
|
172
|
+
self.agent.shape = self.uniform_shape(state, laya_questions)
|
|
173
|
+
result = self.agent.predict(state, laya_questions)
|
|
174
|
+
except ValueError as error:
|
|
175
|
+
raise BadRequest(str(error)) from error
|
|
176
|
+
finally:
|
|
177
|
+
self.agent.shape = original_shape
|
|
178
|
+
self.requests += 1
|
|
179
|
+
elapsed = time.perf_counter() - started
|
|
180
|
+
|
|
181
|
+
metadata = {
|
|
182
|
+
qid: {"confidence": answer.get("confidence"), "actProbability": answer.get("action", {}).get("act_probability")}
|
|
183
|
+
for qid, answer in result["answers"].items()
|
|
184
|
+
}
|
|
185
|
+
usage = result.get("usage", {})
|
|
186
|
+
print(
|
|
187
|
+
f"[{time.strftime('%H:%M:%S')}] evaluate questions={len(questions)} "
|
|
188
|
+
f"tokens={usage.get('input_tokens', 0)} ms={elapsed * 1000:.0f} waited_ms={waited * 1000:.0f}",
|
|
189
|
+
file=sys.stderr,
|
|
190
|
+
flush=True,
|
|
191
|
+
)
|
|
192
|
+
return {
|
|
193
|
+
"answers": {qid: from_laya_answer(answer) for qid, answer in result["answers"].items()},
|
|
194
|
+
"usage": {"inputTokens": usage.get("input_tokens", 0), "outputTokens": 0},
|
|
195
|
+
"warnings": self.diagnostics(state, laya_questions),
|
|
196
|
+
"providerMetadata": {"laya": {"model": self.model_id, "latencyMs": round(elapsed * 1000, 1), "answers": metadata}},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
def warm_up(self):
|
|
200
|
+
"""Core ML compiles each enumerated input length on first use (seconds). Pay that before real traffic."""
|
|
201
|
+
lengths = list(self.shape.get("lengths") or [self.shape.get("default_length", 128)])
|
|
202
|
+
# Finish on the bucket real traffic starts in, so the first request does not pay a shape switch.
|
|
203
|
+
resting = next((n for n in lengths if n >= self.min_length), lengths[-1])
|
|
204
|
+
lengths = [n for n in lengths if n != resting] + [resting]
|
|
205
|
+
question = {"warm": {"type": "noul", "instructions": "Is this a warm-up request?"}}
|
|
206
|
+
started = time.perf_counter()
|
|
207
|
+
for length in lengths:
|
|
208
|
+
# "a " is roughly one token; the prefix takes about 30, so this lands in the bucket for `length`.
|
|
209
|
+
state = "a " * max(1, length - 40)
|
|
210
|
+
try:
|
|
211
|
+
with self.lock:
|
|
212
|
+
original_shape = self.agent.shape
|
|
213
|
+
self.agent.shape = dict(original_shape, lengths=[length])
|
|
214
|
+
try:
|
|
215
|
+
self.agent.predict(state, question)
|
|
216
|
+
finally:
|
|
217
|
+
self.agent.shape = original_shape
|
|
218
|
+
except Exception as error: # noqa: BLE001 - a failed warm-up only costs latency later
|
|
219
|
+
print(f"warm-up for length {length} failed: {type(error).__name__}", file=sys.stderr, flush=True)
|
|
220
|
+
self.warm = True
|
|
221
|
+
print(f"warm-up finished in {time.perf_counter() - started:.1f}s ({len(lengths)} shapes)", file=sys.stderr, flush=True)
|
|
222
|
+
|
|
223
|
+
def health(self):
|
|
224
|
+
return {
|
|
225
|
+
"status": "ok" if self.warm else "warming",
|
|
226
|
+
"model": self.model_id,
|
|
227
|
+
"computeUnits": getattr(self.agent, "compute_units", None),
|
|
228
|
+
"maxLength": self.shape.get("max_length"),
|
|
229
|
+
"maxOptions": self.shape.get("max_options"),
|
|
230
|
+
"batchSize": self.shape.get("batch_size"),
|
|
231
|
+
"requests": self.requests,
|
|
232
|
+
"uptimeSeconds": round(time.time() - self.started_at),
|
|
233
|
+
"pid": os.getpid(),
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def make_handler(judge):
|
|
238
|
+
class Handler(BaseHTTPRequestHandler):
|
|
239
|
+
server_version = "mu-local-judge/0.1"
|
|
240
|
+
|
|
241
|
+
def log_message(self, *_args):
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
def send_json(self, status, payload):
|
|
245
|
+
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
246
|
+
self.send_response(status)
|
|
247
|
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
248
|
+
self.send_header("Content-Length", str(len(body)))
|
|
249
|
+
self.end_headers()
|
|
250
|
+
self.wfile.write(body)
|
|
251
|
+
|
|
252
|
+
def send_failure(self, status, message):
|
|
253
|
+
self.send_json(status, {"error": {"message": message}})
|
|
254
|
+
|
|
255
|
+
def do_GET(self):
|
|
256
|
+
if self.path == "/health":
|
|
257
|
+
self.send_json(200, judge.health())
|
|
258
|
+
else:
|
|
259
|
+
self.send_failure(404, "Not found")
|
|
260
|
+
|
|
261
|
+
def do_POST(self):
|
|
262
|
+
if self.path != "/evaluate":
|
|
263
|
+
return self.send_failure(404, "Not found")
|
|
264
|
+
# Browsers always send Origin on cross-site POSTs; nothing legitimate calls this from a web page.
|
|
265
|
+
if self.headers.get("Origin"):
|
|
266
|
+
return self.send_failure(403, "Browser requests are not allowed")
|
|
267
|
+
if "application/json" not in (self.headers.get("Content-Type") or ""):
|
|
268
|
+
return self.send_failure(415, "Content-Type must be application/json")
|
|
269
|
+
try:
|
|
270
|
+
length = int(self.headers.get("Content-Length") or 0)
|
|
271
|
+
except ValueError:
|
|
272
|
+
return self.send_failure(400, "Invalid Content-Length")
|
|
273
|
+
if length <= 0 or length > MAX_BODY_BYTES:
|
|
274
|
+
return self.send_failure(413, f"Body must be between 1 and {MAX_BODY_BYTES} bytes")
|
|
275
|
+
try:
|
|
276
|
+
payload = json.loads(self.rfile.read(length))
|
|
277
|
+
except (ValueError, UnicodeDecodeError):
|
|
278
|
+
return self.send_failure(400, "Body is not valid JSON")
|
|
279
|
+
if not isinstance(payload, dict):
|
|
280
|
+
return self.send_failure(400, "Body must be a JSON object")
|
|
281
|
+
try:
|
|
282
|
+
self.send_json(200, judge.evaluate(payload.get("state"), payload.get("questions")))
|
|
283
|
+
except BadRequest as error:
|
|
284
|
+
self.send_failure(400, str(error)[:300])
|
|
285
|
+
except Exception as error: # noqa: BLE001 - the kernel fails open on a 500
|
|
286
|
+
print(f"evaluate failed: {type(error).__name__}: {error}", file=sys.stderr, flush=True)
|
|
287
|
+
self.send_failure(500, f"Local judge failed: {type(error).__name__}")
|
|
288
|
+
|
|
289
|
+
return Handler
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main():
|
|
293
|
+
parser = argparse.ArgumentParser(description="mu local judge sidecar (Laya on Core ML)")
|
|
294
|
+
parser.add_argument("--model-dir", default=setting("LOCAL_JUDGE_MODEL", str(DEFAULT_MODEL_DIR)))
|
|
295
|
+
parser.add_argument("--host", default=DEFAULT_HOST)
|
|
296
|
+
parser.add_argument("--port", type=int, default=int(setting("LOCAL_JUDGE_PORT", DEFAULT_PORT)))
|
|
297
|
+
parser.add_argument("--compute-units", choices=["all", "cpu", "cpu_gpu", "cpu_ne"])
|
|
298
|
+
parser.add_argument("--no-warmup", action="store_true")
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"--min-length",
|
|
301
|
+
type=int,
|
|
302
|
+
default=int(setting("LOCAL_JUDGE_MIN_LENGTH", DEFAULT_MIN_LENGTH)),
|
|
303
|
+
help="Smallest input length to pad to. A floor above the usual request size avoids shape switches between requests.",
|
|
304
|
+
)
|
|
305
|
+
args = parser.parse_args()
|
|
306
|
+
|
|
307
|
+
if args.host not in ("127.0.0.1", "localhost", "::1"):
|
|
308
|
+
parser.error("The local judge has no authentication and only binds to loopback")
|
|
309
|
+
|
|
310
|
+
judge = LocalJudge(args.model_dir, compute_units=args.compute_units, min_length=args.min_length)
|
|
311
|
+
server = ThreadingHTTPServer((args.host, args.port), make_handler(judge))
|
|
312
|
+
server.daemon_threads = True
|
|
313
|
+
print(
|
|
314
|
+
f"mu local judge: {judge.model_id} loaded in {judge.load_seconds:.1f}s, "
|
|
315
|
+
f"listening on http://{args.host}:{args.port}",
|
|
316
|
+
file=sys.stderr,
|
|
317
|
+
flush=True,
|
|
318
|
+
)
|
|
319
|
+
if args.no_warmup:
|
|
320
|
+
judge.warm = True
|
|
321
|
+
else:
|
|
322
|
+
threading.Thread(target=judge.warm_up, daemon=True).start()
|
|
323
|
+
try:
|
|
324
|
+
server.serve_forever()
|
|
325
|
+
except KeyboardInterrupt:
|
|
326
|
+
pass
|
|
327
|
+
finally:
|
|
328
|
+
server.server_close()
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
if __name__ == "__main__":
|
|
332
|
+
main()
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mu-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "mu: a coding agent that thinks before it acts. A small, fast judge makes the routine calls; the big model keeps its attention for the work. Built on pi.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mu": "kyrn/bin/mu.mjs"
|
|
8
|
+
},
|
|
9
|
+
"piConfig": {
|
|
10
|
+
"name": "mu",
|
|
11
|
+
"configDir": ".mu"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"coding-agent",
|
|
15
|
+
"ai",
|
|
16
|
+
"llm",
|
|
17
|
+
"cli",
|
|
18
|
+
"terminal",
|
|
19
|
+
"agent",
|
|
20
|
+
"judge",
|
|
21
|
+
"pi"
|
|
22
|
+
],
|
|
23
|
+
"homepage": "https://github.com/qybaihe/MU",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/qybaihe/MU.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/qybaihe/MU/issues"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22.19.0"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@earendil-works/chord": "0.86.0",
|
|
37
|
+
"@silvia-odwyer/photon-node": "0.3.4",
|
|
38
|
+
"jiti": "2.7.0"
|
|
39
|
+
},
|
|
40
|
+
"muBuild": {
|
|
41
|
+
"pi": "0.86.0",
|
|
42
|
+
"judge": "0.1.0",
|
|
43
|
+
"commit": "d5e481d01"
|
|
44
|
+
}
|
|
45
|
+
}
|