harness-sdk-python 0.31.2__py3-none-any.whl → 0.32.1__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.
@@ -342,7 +342,6 @@ class RunManager(Tray):
342
342
  "dispatching": dispatching,
343
343
  "stopping": stopping,
344
344
  "nextDispatch": nextDispatch,
345
- "inputRequests": inputRequests,
346
345
  "error": error,
347
346
  "stopReason": stopReason,
348
347
  }
@@ -370,9 +369,14 @@ class RunManager(Tray):
370
369
  self.state["runId"] = self.id
371
370
  self._hydrate_machine()
372
371
  self._mirror_steer()
373
- unsubscribe = self.steerQueue.subscribe(lambda _op: self._mirror_steer())
372
+ self._mirror_input()
373
+ unsubscribes = [
374
+ self.steerQueue.subscribe(lambda _op: self._mirror_steer()),
375
+ self.inputRequests.subscribe(lambda _op: self._mirror_input()),
376
+ ]
374
377
  yield
375
- unsubscribe()
378
+ for unsubscribe in unsubscribes:
379
+ unsubscribe()
376
380
 
377
381
  class Thread(Protocol):
378
382
  """The thread projection RunManager reads; the host owns the tree."""
@@ -500,6 +504,14 @@ class RunManager(Tray):
500
504
  def _mirror_steer(self) -> None:
501
505
  self.steerView.value = [_public(item) for item in self.steerQueue.value]
502
506
 
507
+ def _mirror_input(self) -> None:
508
+ # None publishes as an absent key, matching the TS harness shape
509
+ requests = self.inputRequests.value
510
+ if requests is None:
511
+ self.state.pop("inputRequests", None)
512
+ else:
513
+ self.state["inputRequests"] = requests
514
+
503
515
  def _sync_steering(self) -> None:
504
516
  ctx = self._run_live
505
517
  if ctx is None:
harness_sdk/voice.py CHANGED
@@ -7,9 +7,10 @@ lives in its own ephemeral tray, minted by ``voice/start`` and deleted when
7
7
  the session ends. The host implements ``VoiceManager.Host``: the seed read
8
8
  and the turn commit both stay host-owned.
9
9
 
10
- ``voice/start {offer, tools?}`` seeds the provider from the thread
11
- transcript (at most 128 messages, tail taken), hands the client's frontend
12
- function tool definitions to the provider, and projects ``state["voice"] =
10
+ ``voice/start {offer}`` seeds the provider from the thread
11
+ transcript (at most 128 messages, tail taken), hands the caller's frontend
12
+ function tool definitions (the ``tools`` map the client's synced model
13
+ context stores at ``ctx.caller.context``) to the provider, and projects ``state["voice"] =
13
14
  {clientId, status, toolCalls, activeTask}`` — the SDP answer rides only the command's
14
15
  result payload ``{answer}``. A named call matching a client-declared tool
15
16
  projects into ``toolCalls`` as ``{callId, name, arguments}``; the owning
@@ -74,28 +75,32 @@ def _reject(reason: str, message: str) -> StatewireReject:
74
75
  return StatewireReject(message, payload={"reason": reason})
75
76
 
76
77
 
77
- def _validated_tools(tools: Any) -> "list[VoiceManager.ToolDef]":
78
+ def _context_tools(context: Any) -> "list[VoiceManager.ToolDef]":
79
+ tools = context.get("tools") if isinstance(context, dict) else None
78
80
  if tools is None:
79
81
  return []
80
- if not isinstance(tools, list):
81
- raise _reject("invalid-message", "tools must be a list")
82
- names: set[str] = set()
83
- for tool in tools:
82
+ if not isinstance(tools, dict):
83
+ raise _reject("invalid-message", "context tools must be a name-keyed object")
84
+ defs: "list[VoiceManager.ToolDef]" = []
85
+ for name, spec in tools.items():
84
86
  if (
85
- not isinstance(tool, dict)
86
- or not isinstance(tool.get("name"), str)
87
- or tool["name"] == ""
88
- or not isinstance(tool.get("description"), str)
89
- or not isinstance(tool.get("parameters"), dict)
87
+ name == ""
88
+ or not isinstance(spec, dict)
89
+ or not isinstance(spec.get("description", ""), str)
90
+ or not isinstance(spec.get("parameters"), dict)
90
91
  ):
91
92
  raise _reject(
92
93
  "invalid-message",
93
- f"tools entries must be {{name, description, parameters}}, got {tool!r}",
94
+ f"context tools entries must be name -> {{description?, parameters}}, got {name!r}: {spec!r}",
94
95
  )
95
- if tool["name"] in names:
96
- raise _reject("invalid-message", f"duplicate tool name {tool['name']!r}")
97
- names.add(tool["name"])
98
- return tools
96
+ defs.append(
97
+ {
98
+ "name": name,
99
+ "description": spec.get("description", ""),
100
+ "parameters": spec["parameters"],
101
+ }
102
+ )
103
+ return defs
99
104
 
100
105
 
101
106
  def _capped_seed(seed: Any) -> "list[VoiceManager.SeedMessage]":
@@ -156,7 +161,7 @@ class VoiceManager(Tray):
156
161
  text: str
157
162
 
158
163
  class ToolDef(TypedDict):
159
- """One frontend function tool the client declares at ``voice/start``."""
164
+ """One frontend function tool from the caller's synced model context."""
160
165
 
161
166
  name: str
162
167
  description: str
@@ -593,7 +598,7 @@ class VoiceManagerNamespace(TrayMapNamespace):
593
598
  offer = params.get("offer")
594
599
  if not isinstance(offer, str) or offer == "":
595
600
  raise _reject("invalid-message", "offer must be a non-empty SDP string")
596
- tools = _validated_tools(params.get("tools"))
601
+ tools = _context_tools(ctx.caller.context)
597
602
  if self._starting:
598
603
  raise _reject("voice-active", "a voice session is already live")
599
604
  session = self.live()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: harness-sdk-python
3
- Version: 0.31.2
3
+ Version: 0.32.1
4
4
  Summary: RunManager: the harness-sdk runs subsystem for Python Statewire hosts
5
5
  Project-URL: Repository, https://github.com/assistant-ui/harness-sdk
6
6
  License-Expression: MIT
@@ -3,8 +3,8 @@ harness_sdk/checkpoints.py,sha256=L02l1IyLDj2b-GD7_HGrck7uenALeI4kg8yJSzoNVmc,25
3
3
  harness_sdk/deepagents.py,sha256=cMipZ9ndZ3zUR3-VqOMf5CiPVMmym74ytIeguQAjYZo,12280
4
4
  harness_sdk/fenced_postgres.py,sha256=hbbrMR1WaqrHTNgd4wFrXfRrzQFGnyim-c8nZXgwWp4,13235
5
5
  harness_sdk/linear_thread.py,sha256=laDRXKLFsMlyWS4snqNAM6t38wO26gevLWXRNivhHSM,1810
6
- harness_sdk/run_manager.py,sha256=CbY_4dpFeqGx3kJNI1ffsu3uIvzVc-Tc1UB6Nr9SiHY,77654
7
- harness_sdk/voice.py,sha256=BgMYJRVOVYTJgXOWSWgzJScGAVFD7WyUXMa2_LJghwI,28381
8
- harness_sdk_python-0.31.2.dist-info/METADATA,sha256=RRfWhlnnjOMYHLyLsTEVKTVCrw7b0hhuAYzcxQ2hJFc,2200
9
- harness_sdk_python-0.31.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
10
- harness_sdk_python-0.31.2.dist-info/RECORD,,
6
+ harness_sdk/run_manager.py,sha256=ECj7j_xkGn81IngEEGA3MWXUejk9ZqNcH6vzxG6o9yU,78085
7
+ harness_sdk/voice.py,sha256=KtUHmwfZQCwEvJaIolc-XgjAhxvltfMYEI5ycWcw53M,28590
8
+ harness_sdk_python-0.32.1.dist-info/METADATA,sha256=dEQKSIIg_0xmdy6B7wiHhjG8g2VdDIaeR8_mQXps5KA,2200
9
+ harness_sdk_python-0.32.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
10
+ harness_sdk_python-0.32.1.dist-info/RECORD,,