kinloom 1.0.0__py3-none-any.whl
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.
- kinloom/__init__.py +19 -0
- kinloom/__main__.py +22 -0
- kinloom/add.py +149 -0
- kinloom/brain.py +3504 -0
- kinloom/bundle.py +106 -0
- kinloom/check.py +271 -0
- kinloom/cli.py +960 -0
- kinloom/clients/cpp/kinloom.hpp +12357 -0
- kinloom/clients/csharp/Kinloom.asmdef +14 -0
- kinloom/clients/csharp/Kinloom.asmdef.meta +7 -0
- kinloom/clients/csharp/Kinloom.cs +8930 -0
- kinloom/clients/csharp/Kinloom.cs.meta +11 -0
- kinloom/clients/csharp/Kinloom.csproj +22 -0
- kinloom/clients/csharp/Kinloom.csproj.meta +7 -0
- kinloom/clients/csharp/Unity/Kinloom.Unity.asmdef +16 -0
- kinloom/clients/csharp/Unity/Kinloom.Unity.asmdef.meta +7 -0
- kinloom/clients/csharp/Unity/KinloomDialogue.cs +264 -0
- kinloom/clients/csharp/Unity/KinloomDialogue.cs.meta +11 -0
- kinloom/clients/csharp/Unity.meta +8 -0
- kinloom/clients/csharp/package.json +21 -0
- kinloom/clients/csharp/package.json.meta +7 -0
- kinloom/clients/godot/addons/kinloom/ai_character.gd +183 -0
- kinloom/clients/godot/addons/kinloom/ai_dialogue.gd +255 -0
- kinloom/clients/godot/addons/kinloom/ai_mind.gd +138 -0
- kinloom/clients/godot/addons/kinloom/kinloom.gd +1404 -0
- kinloom/clients/godot/addons/kinloom/native/brain.gd +3216 -0
- kinloom/clients/godot/addons/kinloom/native/http.gd +215 -0
- kinloom/clients/godot/addons/kinloom/native/lines.gd +459 -0
- kinloom/clients/godot/addons/kinloom/native/memory.gd +245 -0
- kinloom/clients/godot/addons/kinloom/native/minds.gd +139 -0
- kinloom/clients/godot/addons/kinloom/native/model.gd +123 -0
- kinloom/clients/godot/addons/kinloom/native/prompts.gd +550 -0
- kinloom/clients/godot/addons/kinloom/native/providers.gd +602 -0
- kinloom/clients/godot/addons/kinloom/native/py.gd +958 -0
- kinloom/clients/godot/addons/kinloom/native/rules.gd +621 -0
- kinloom/clients/godot/addons/kinloom/native/shape.gd +331 -0
- kinloom/clients/godot/addons/kinloom/native/souls.gd +564 -0
- kinloom/clients/godot/addons/kinloom/native/stream.gd +377 -0
- kinloom/clients/godot/addons/kinloom/native/vectors.gd +302 -0
- kinloom/clients/godot/addons/kinloom/plugin.cfg +7 -0
- kinloom/clients/godot/addons/kinloom/plugin.gd +118 -0
- kinloom/clients/java/Kinloom.java +9068 -0
- kinloom/clients/lua/kinloom-1.0.0-1.rockspec +22 -0
- kinloom/clients/lua/kinloom.lua +10493 -0
- kinloom/clients/rust/Cargo.lock +7 -0
- kinloom/clients/rust/Cargo.toml +14 -0
- kinloom/clients/rust/kinloom.rs +15040 -0
- kinloom/clients/rust/lib.rs +5 -0
- kinloom/clients/web/kinloom-talk.js +259 -0
- kinloom/clients/web/kinloom.d.ts +1101 -0
- kinloom/clients/web/kinloom.js +7695 -0
- kinloom/clients/web/kinloom.ts +6788 -0
- kinloom/clients/web/package.json +36 -0
- kinloom/clients/web/tsconfig.json +14 -0
- kinloom/config.py +123 -0
- kinloom/dashboard.html +207 -0
- kinloom/demo.py +112 -0
- kinloom/deploy.py +130 -0
- kinloom/harness.py +413 -0
- kinloom/init.py +601 -0
- kinloom/kit/brief.md +40 -0
- kinloom/kit/calls.md +274 -0
- kinloom/kit/cookbook.md +431 -0
- kinloom/kit/engines/cpp.md +50 -0
- kinloom/kit/engines/csharp.md +68 -0
- kinloom/kit/engines/godot.md +79 -0
- kinloom/kit/engines/http.md +37 -0
- kinloom/kit/engines/java.md +50 -0
- kinloom/kit/engines/lua.md +56 -0
- kinloom/kit/engines/python.md +71 -0
- kinloom/kit/engines/rust.md +50 -0
- kinloom/kit/engines/web.md +61 -0
- kinloom/kit/guide.md +45 -0
- kinloom/kit/mod.md +97 -0
- kinloom/kit/openapi.json +2863 -0
- kinloom/mcp.py +329 -0
- kinloom/media.py +84 -0
- kinloom/memory.py +214 -0
- kinloom/minds.py +586 -0
- kinloom/mod.py +340 -0
- kinloom/openapi.py +153 -0
- kinloom/probe.py +163 -0
- kinloom/prompts.py +552 -0
- kinloom/providers.py +684 -0
- kinloom/recipe.py +185 -0
- kinloom/remote.py +530 -0
- kinloom/server.py +934 -0
- kinloom/shapes.py +457 -0
- kinloom/ship.py +278 -0
- kinloom/souls.py +713 -0
- kinloom/talk.html +470 -0
- kinloom/talk.py +374 -0
- kinloom/testing.py +281 -0
- kinloom/voice.py +717 -0
- kinloom-1.0.0.dist-info/METADATA +422 -0
- kinloom-1.0.0.dist-info/RECORD +100 -0
- kinloom-1.0.0.dist-info/WHEEL +5 -0
- kinloom-1.0.0.dist-info/entry_points.txt +2 -0
- kinloom-1.0.0.dist-info/licenses/LICENSE +21 -0
- kinloom-1.0.0.dist-info/top_level.txt +1 -0
kinloom/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Kinloom: deep AI in any game, any engine, any model, in minutes.
|
|
2
|
+
|
|
3
|
+
from kinloom import Kinloom
|
|
4
|
+
kl = Kinloom()
|
|
5
|
+
kl.character("VEGA", "an over-excited race commentator")
|
|
6
|
+
kl.say("VEGA", "overtake", {"who": "BLAZE", "whom": "NOVA", "gap": 0.4})
|
|
7
|
+
"""
|
|
8
|
+
from .brain import Brain, Kinloom
|
|
9
|
+
from .shapes import Shape
|
|
10
|
+
|
|
11
|
+
__all__ = ["Kinloom", "Brain", "Shape"]
|
|
12
|
+
__version__ = "1.0.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __getattr__(name):
|
|
16
|
+
"""`from kinloom import character` (a call, not a module name): say how it is written, so the fix is one line."""
|
|
17
|
+
if hasattr(Kinloom, name) and not name.startswith("_"):
|
|
18
|
+
raise ImportError("kinloom.%s is a call on a Kinloom object: kl = Kinloom() once, then kl.%s(...)" % (name, name))
|
|
19
|
+
raise AttributeError("module 'kinloom' has no attribute %r" % name)
|
kinloom/__main__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""`python -m kinloom ...`: the same as the `kinloom` command (when pip's scripts folder is not on PATH).
|
|
2
|
+
|
|
3
|
+
Inside a Python game, `python -m` finds the game's own copy first (kinloom/ next to the game, written by `kinloom
|
|
4
|
+
init`): the installed Kinloom is used instead when there is one, so `python -m kinloom init` updates that copy."""
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
_here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
9
|
+
if os.path.normcase(_here) == os.path.normcase(os.getcwd()) and os.path.exists(os.path.join(_here, "kinloom", "brain.py")):
|
|
10
|
+
_kept = sys.path[:]
|
|
11
|
+
sys.path[:] = [p for p in sys.path if os.path.normcase(os.path.abspath(p or ".")) != os.path.normcase(_here)]
|
|
12
|
+
_local = {m: sys.modules.pop(m) for m in list(sys.modules) if m == "kinloom" or m.startswith("kinloom.")}
|
|
13
|
+
try:
|
|
14
|
+
from kinloom.cli import main # the installed one
|
|
15
|
+
except ImportError: # only the game's copy exists: use it
|
|
16
|
+
sys.path[:] = _kept
|
|
17
|
+
sys.modules.update(_local)
|
|
18
|
+
from kinloom.cli import main
|
|
19
|
+
else:
|
|
20
|
+
from .cli import main
|
|
21
|
+
|
|
22
|
+
raise SystemExit(main())
|
kinloom/add.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""`kinloom add NAME "who they are"`: a character the player can talk to, in one command. Writes it into kinloom.json
|
|
2
|
+
(no declaration code in the game, any engine) and prints the lines to call it in the game's own language."""
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from . import config as _config
|
|
8
|
+
|
|
9
|
+
SNIPPETS = {
|
|
10
|
+
"gdscript": '''# The player talks to {name} (text from a LineEdit; "" when they walk up: {name} speaks first). ~1 s, the game runs on.
|
|
11
|
+
var reply: String = await Kinloom.chat("{name}", text, {{"gold": gold}}, player_name)
|
|
12
|
+
for a in Kinloom.act("{name}", {{}}, false): # what {name} decided while talking, validated
|
|
13
|
+
if a.tool == "give": give_item(a.args.item)
|
|
14
|
+
var soul = await Kinloom.soul("{name}", player_name) # soul.mood.word, soul.gesture: animations
|
|
15
|
+
# No code at all: an AIDialogue node with character_name = "{name}", then call open().''',
|
|
16
|
+
"csharp": '''// The player talks to {name} ("" when they walk up: {name} speaks first). ~1 s: awaited, the game runs on.
|
|
17
|
+
string reply = await kl.Chat("{name}", text, new Dictionary<string, object> {{ ["gold"] = gold }}, playerName);
|
|
18
|
+
foreach (var a in kl.Act("{name}", null, false)) if (a.Tool == "give") GiveItem((string)a.Args["item"]);
|
|
19
|
+
var soul = await kl.Soul("{name}", playerName); // soul["mood"], soul["gesture"]: animations''',
|
|
20
|
+
"js": '''// The player talks to {name} ("" when they walk up: {name} speaks first). Streamed as it is written.
|
|
21
|
+
const reply = await kl.chat("{name}", text, {{ gold }}, playerName, (piece) => (bubble.textContent += piece));
|
|
22
|
+
for (const a of kl.act("{name}", {{}}, false)) if (a.tool === "give") giveItem(a.args.item); // decided while talking
|
|
23
|
+
const soul = await kl.soul("{name}", playerName); // soul.mood.word, soul.gesture: animations
|
|
24
|
+
// Ready UI: import {{ talkBox }} from "./kinloom/kinloom-talk.js"; talkBox(kl, "{name}", {{ speaker: playerName }}).open();''',
|
|
25
|
+
"python": '''# Pygame / pygame-ce: a ready dialogue box (typing, Enter sends, Esc closes, replies stream in, never blocks).
|
|
26
|
+
from kinloom.talk import PygameTalkBox
|
|
27
|
+
box = PygameTalkBox(kl, "{name}", speaker=player_name, facts=lambda: {{"gold": gold}},
|
|
28
|
+
on_action=lambda tool, args: give_item(args["item"])) # what {name} decides while talking
|
|
29
|
+
# events: if e.type == pygame.KEYDOWN and e.key == pygame.K_t and not box.is_open: box.open() ({name} speaks first)
|
|
30
|
+
# if box.handle(e): continue # the box used the key: not for the game
|
|
31
|
+
# frame: box.update(); box.draw(screen) # box.mood, box.gesture: animations
|
|
32
|
+
# Other Python engines: kinloom.talk.Talk (same, without drawing: talk.send(text), talk.update(), talk.lines).''',
|
|
33
|
+
"lua": '''-- The player talks to {name} ("" when they walk up: {name} speaks first); the reply arrives in the callback.
|
|
34
|
+
kl:chat("{name}", text, {{ gold = gold }}, function(reply) dialogue = reply end, player_name)
|
|
35
|
+
for _, a in ipairs(kl:act("{name}", {{}}, false)) do if a.tool == "give" then give_item(a.args.item) end end
|
|
36
|
+
kl:soul("{name}", player_name, function(soul) mood = soul and soul.mood.word end)''',
|
|
37
|
+
"rust": '''// The player talks to {name} ("" when they walk up: {name} speaks first). Blocking ~1 s: run it on a thread.
|
|
38
|
+
let reply = kl.chat_as("{name}", &text, obj! {{ "gold" => gold }}, &player_name);
|
|
39
|
+
for a in kl.act_with("{name}", obj! {{}}, false) {{ if a.tool == "give" {{ give_item(a.args.get("item").as_str()); }} }}
|
|
40
|
+
let soul = kl.soul("{name}", &player_name); // Option<Json>: mood, gesture''',
|
|
41
|
+
"java": '''// The player talks to {name} ("" when they walk up: {name} speaks first). ~1 s: off the game thread.
|
|
42
|
+
String reply = kl.chat("{name}", text, map("gold", gold), playerName);
|
|
43
|
+
for (Kinloom.Action a : kl.act("{name}", map(), false)) if (a.tool.equals("give")) giveItem((String) a.args.get("item"));
|
|
44
|
+
Map<String, Object> soul = kl.soul("{name}", playerName); // mood, gesture''',
|
|
45
|
+
"cpp": '''// The player talks to {name} ("" when they walk up: {name} speaks first). ~1 s: off the game loop.
|
|
46
|
+
std::string reply = kl.chat("{name}", text, obj({{{{"gold", gold}}}}), player_name);
|
|
47
|
+
for (auto& a : kl.act("{name}", obj({{}}), false)) if (a.tool == "give") give_item(a.args["item"].str());
|
|
48
|
+
kinloom::Json soul = kl.soul("{name}", player_name); // soul["mood"]["word"], soul["gesture"]''',
|
|
49
|
+
"http": '''POST /v1/chat {{"name": "{name}", "message": "<what the player said, or \\"\\">", "speaker": "<player name>", "facts": {{}}}}
|
|
50
|
+
-> {{"text", "mood", "feeling", "gesture"}} (add "stream": true for server-sent events)
|
|
51
|
+
POST /v1/act {{"name": "{name}", "think": false}} -> {{"actions": [{{"tool", "args"}}]}} (decided while talking)''',
|
|
52
|
+
}
|
|
53
|
+
LANGUAGE = {"godot": "gdscript", "unity": "csharp", "dotnet": "csharp", "web": "js", "python": "python", "love": "lua",
|
|
54
|
+
"defold": "lua", "rust": "rust", "java": "java", "cpp": "cpp", "unreal": "cpp", "other": "http"}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_tools(text):
|
|
58
|
+
""""give(item), follow, sell(item, price)" -> {"give": {"item": "text"}, "follow": {}, "sell": {...}}."""
|
|
59
|
+
tools = {}
|
|
60
|
+
for m in re.finditer(r"([A-Za-z_]\w*)\s*(?:\(([^)]*)\))?", text or ""):
|
|
61
|
+
args = {}
|
|
62
|
+
for a in (x.strip() for x in (m.group(2) or "").split(",")):
|
|
63
|
+
if re.match(r"^[A-Za-z_]\w*$", a):
|
|
64
|
+
args[a] = [0, 100] if a in ("price", "amount", "count", "gold", "quantity") else "text"
|
|
65
|
+
tools[m.group(1)] = args
|
|
66
|
+
return tools
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def parse_trust(items):
|
|
70
|
+
"""["give=0.3"] -> {"give": 0.3}"""
|
|
71
|
+
out = {}
|
|
72
|
+
for item in items or []:
|
|
73
|
+
name, _, value = str(item).partition("=")
|
|
74
|
+
try:
|
|
75
|
+
out[name.strip()] = max(-1.0, min(1.0, float(value)))
|
|
76
|
+
except ValueError:
|
|
77
|
+
raise ValueError("--trust takes tool=feeling, e.g. give=0.3 (got %r)" % item)
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def update_config(folder, name, entry, lore=()):
|
|
82
|
+
"""Adds or replaces one character (and appends lore) in kinloom.json, keeping everything else. Returns the path."""
|
|
83
|
+
path = os.path.join(folder, _config.FILE)
|
|
84
|
+
data = {}
|
|
85
|
+
if os.path.exists(path):
|
|
86
|
+
try:
|
|
87
|
+
with open(path, encoding="utf-8") as f:
|
|
88
|
+
data = json.load(f)
|
|
89
|
+
except (OSError, ValueError) as e:
|
|
90
|
+
raise _config.ConfigError("%s is not valid JSON (%s): fix it first, nothing was changed" % (path, e))
|
|
91
|
+
if not isinstance(data, dict):
|
|
92
|
+
raise _config.ConfigError("%s must be a JSON object: nothing was changed" % path)
|
|
93
|
+
chars = data.get("characters") if isinstance(data.get("characters"), dict) else {}
|
|
94
|
+
if name:
|
|
95
|
+
chars[name] = entry
|
|
96
|
+
data["characters"] = chars
|
|
97
|
+
if lore:
|
|
98
|
+
facts = data.get("lore") if isinstance(data.get("lore"), list) else []
|
|
99
|
+
data["lore"] = facts + [x for x in lore if x not in facts]
|
|
100
|
+
tmp = path + ".tmp"
|
|
101
|
+
with open(tmp, "w", encoding="utf-8") as f:
|
|
102
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
103
|
+
f.write("\n")
|
|
104
|
+
os.replace(tmp, path)
|
|
105
|
+
return path
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def add(folder, name, soul="", tools="", trust=(), offline="", language=None, strict=False, lore=(), engine=None, voice="",
|
|
109
|
+
max_chars=None):
|
|
110
|
+
"""Writes the character into kinloom.json and returns the text to show: what was written and the code to call it."""
|
|
111
|
+
from . import init as _init
|
|
112
|
+
name = (name or "").strip()
|
|
113
|
+
if not name and not lore:
|
|
114
|
+
raise ValueError("a name is needed (or --lore facts)")
|
|
115
|
+
entry = {}
|
|
116
|
+
if name:
|
|
117
|
+
entry["soul"] = soul.strip() if soul and soul.strip() else True
|
|
118
|
+
entry["offline"] = offline or "..."
|
|
119
|
+
t = parse_tools(tools)
|
|
120
|
+
if t:
|
|
121
|
+
entry["tools"] = t
|
|
122
|
+
tr = parse_trust(trust)
|
|
123
|
+
unknown = [k for k in tr if k not in t]
|
|
124
|
+
if unknown:
|
|
125
|
+
raise ValueError("--trust names tools that are not in --tools: %s" % ", ".join(unknown))
|
|
126
|
+
if tr:
|
|
127
|
+
entry["trust"] = tr
|
|
128
|
+
if language:
|
|
129
|
+
entry["language"] = language
|
|
130
|
+
if strict:
|
|
131
|
+
entry["invent"] = False
|
|
132
|
+
if voice and voice.strip():
|
|
133
|
+
entry["voice"] = voice.strip()
|
|
134
|
+
if max_chars:
|
|
135
|
+
n = int(max_chars)
|
|
136
|
+
if n < 10:
|
|
137
|
+
raise ValueError("--max-chars must be at least 10 (the characters a text box holds)")
|
|
138
|
+
entry["max_chars"] = n
|
|
139
|
+
path = update_config(folder, name, entry, [x.strip() for x in lore if x and x.strip()])
|
|
140
|
+
engine = engine or _init.detect(folder)
|
|
141
|
+
lang = LANGUAGE.get(engine, "http")
|
|
142
|
+
out = ["kinloom.json: %s%s" % (("character %s " % name) if name else "", "and %d world fact(s)" % len(lore) if lore else "written"),
|
|
143
|
+
"(%s)" % os.path.abspath(path)]
|
|
144
|
+
if name:
|
|
145
|
+
out += ["", "No declaration code needed: every engine and session gets %s from kinloom.json (edits apply while the game runs)." % name,
|
|
146
|
+
"Call it from the game (%s):" % engine, "", SNIPPETS[lang].format(name=name)]
|
|
147
|
+
if entry.get("tools") and not entry.get("trust"):
|
|
148
|
+
out += ["", "Tip: players will try to talk %s into using its tools; bound them with --trust %s=0.3." % (name, next(iter(entry["tools"])))]
|
|
149
|
+
return "\n".join(out)
|