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.
Files changed (100) hide show
  1. kinloom/__init__.py +19 -0
  2. kinloom/__main__.py +22 -0
  3. kinloom/add.py +149 -0
  4. kinloom/brain.py +3504 -0
  5. kinloom/bundle.py +106 -0
  6. kinloom/check.py +271 -0
  7. kinloom/cli.py +960 -0
  8. kinloom/clients/cpp/kinloom.hpp +12357 -0
  9. kinloom/clients/csharp/Kinloom.asmdef +14 -0
  10. kinloom/clients/csharp/Kinloom.asmdef.meta +7 -0
  11. kinloom/clients/csharp/Kinloom.cs +8930 -0
  12. kinloom/clients/csharp/Kinloom.cs.meta +11 -0
  13. kinloom/clients/csharp/Kinloom.csproj +22 -0
  14. kinloom/clients/csharp/Kinloom.csproj.meta +7 -0
  15. kinloom/clients/csharp/Unity/Kinloom.Unity.asmdef +16 -0
  16. kinloom/clients/csharp/Unity/Kinloom.Unity.asmdef.meta +7 -0
  17. kinloom/clients/csharp/Unity/KinloomDialogue.cs +264 -0
  18. kinloom/clients/csharp/Unity/KinloomDialogue.cs.meta +11 -0
  19. kinloom/clients/csharp/Unity.meta +8 -0
  20. kinloom/clients/csharp/package.json +21 -0
  21. kinloom/clients/csharp/package.json.meta +7 -0
  22. kinloom/clients/godot/addons/kinloom/ai_character.gd +183 -0
  23. kinloom/clients/godot/addons/kinloom/ai_dialogue.gd +255 -0
  24. kinloom/clients/godot/addons/kinloom/ai_mind.gd +138 -0
  25. kinloom/clients/godot/addons/kinloom/kinloom.gd +1404 -0
  26. kinloom/clients/godot/addons/kinloom/native/brain.gd +3216 -0
  27. kinloom/clients/godot/addons/kinloom/native/http.gd +215 -0
  28. kinloom/clients/godot/addons/kinloom/native/lines.gd +459 -0
  29. kinloom/clients/godot/addons/kinloom/native/memory.gd +245 -0
  30. kinloom/clients/godot/addons/kinloom/native/minds.gd +139 -0
  31. kinloom/clients/godot/addons/kinloom/native/model.gd +123 -0
  32. kinloom/clients/godot/addons/kinloom/native/prompts.gd +550 -0
  33. kinloom/clients/godot/addons/kinloom/native/providers.gd +602 -0
  34. kinloom/clients/godot/addons/kinloom/native/py.gd +958 -0
  35. kinloom/clients/godot/addons/kinloom/native/rules.gd +621 -0
  36. kinloom/clients/godot/addons/kinloom/native/shape.gd +331 -0
  37. kinloom/clients/godot/addons/kinloom/native/souls.gd +564 -0
  38. kinloom/clients/godot/addons/kinloom/native/stream.gd +377 -0
  39. kinloom/clients/godot/addons/kinloom/native/vectors.gd +302 -0
  40. kinloom/clients/godot/addons/kinloom/plugin.cfg +7 -0
  41. kinloom/clients/godot/addons/kinloom/plugin.gd +118 -0
  42. kinloom/clients/java/Kinloom.java +9068 -0
  43. kinloom/clients/lua/kinloom-1.0.0-1.rockspec +22 -0
  44. kinloom/clients/lua/kinloom.lua +10493 -0
  45. kinloom/clients/rust/Cargo.lock +7 -0
  46. kinloom/clients/rust/Cargo.toml +14 -0
  47. kinloom/clients/rust/kinloom.rs +15040 -0
  48. kinloom/clients/rust/lib.rs +5 -0
  49. kinloom/clients/web/kinloom-talk.js +259 -0
  50. kinloom/clients/web/kinloom.d.ts +1101 -0
  51. kinloom/clients/web/kinloom.js +7695 -0
  52. kinloom/clients/web/kinloom.ts +6788 -0
  53. kinloom/clients/web/package.json +36 -0
  54. kinloom/clients/web/tsconfig.json +14 -0
  55. kinloom/config.py +123 -0
  56. kinloom/dashboard.html +207 -0
  57. kinloom/demo.py +112 -0
  58. kinloom/deploy.py +130 -0
  59. kinloom/harness.py +413 -0
  60. kinloom/init.py +601 -0
  61. kinloom/kit/brief.md +40 -0
  62. kinloom/kit/calls.md +274 -0
  63. kinloom/kit/cookbook.md +431 -0
  64. kinloom/kit/engines/cpp.md +50 -0
  65. kinloom/kit/engines/csharp.md +68 -0
  66. kinloom/kit/engines/godot.md +79 -0
  67. kinloom/kit/engines/http.md +37 -0
  68. kinloom/kit/engines/java.md +50 -0
  69. kinloom/kit/engines/lua.md +56 -0
  70. kinloom/kit/engines/python.md +71 -0
  71. kinloom/kit/engines/rust.md +50 -0
  72. kinloom/kit/engines/web.md +61 -0
  73. kinloom/kit/guide.md +45 -0
  74. kinloom/kit/mod.md +97 -0
  75. kinloom/kit/openapi.json +2863 -0
  76. kinloom/mcp.py +329 -0
  77. kinloom/media.py +84 -0
  78. kinloom/memory.py +214 -0
  79. kinloom/minds.py +586 -0
  80. kinloom/mod.py +340 -0
  81. kinloom/openapi.py +153 -0
  82. kinloom/probe.py +163 -0
  83. kinloom/prompts.py +552 -0
  84. kinloom/providers.py +684 -0
  85. kinloom/recipe.py +185 -0
  86. kinloom/remote.py +530 -0
  87. kinloom/server.py +934 -0
  88. kinloom/shapes.py +457 -0
  89. kinloom/ship.py +278 -0
  90. kinloom/souls.py +713 -0
  91. kinloom/talk.html +470 -0
  92. kinloom/talk.py +374 -0
  93. kinloom/testing.py +281 -0
  94. kinloom/voice.py +717 -0
  95. kinloom-1.0.0.dist-info/METADATA +422 -0
  96. kinloom-1.0.0.dist-info/RECORD +100 -0
  97. kinloom-1.0.0.dist-info/WHEEL +5 -0
  98. kinloom-1.0.0.dist-info/entry_points.txt +2 -0
  99. kinloom-1.0.0.dist-info/licenses/LICENSE +21 -0
  100. 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)