feat: replace draft output bind with general bind

This commit is contained in:
lda
2026-06-28 00:38:42 +07:00 Verified
parent b96ba0f7d1
commit 955c43d808
25 changed files with 1246 additions and 138 deletions
+25 -7
View File
@@ -180,15 +180,27 @@ class WorkflowDraftAuthoringApi:
step = draft_step(workspace.draft, step_id)
capability_name = step.get("use")
if not isinstance(capability_name, str) or not capability_name:
raise ValueError(f"draft step {step_id!r} does not declare a capability use")
raise ValueError(
f"draft step {step_id!r} does not declare a capability use"
)
spec = self.context.specs.get_qualified_spec(capability_name)
source_root, source_parts = _graph_parts(source_path) if not source_path.startswith("local.") else ("local", LocalPath.parse(source_path).parts)
target_root, target_parts = _graph_parts(target_path) if not target_path.startswith("local.") else ("local", LocalPath.parse(target_path).parts)
source_root, source_parts = (
_graph_parts(source_path)
if not source_path.startswith("local.")
else ("local", LocalPath.parse(source_path).parts)
)
target_root, target_parts = (
_graph_parts(target_path)
if not target_path.startswith("local.")
else ("local", LocalPath.parse(target_path).parts)
)
if target_root == "local" and source_root in {"input", "state"}:
local_field = _local_field(target_path)
input_schema = spec.input_schema_contract or spec.input_model.model_json_schema()
input_schema = (
spec.input_schema_contract or spec.input_model.model_json_schema()
)
schema_key = "input_schema" if source_root == "input" else "state_schema"
target_schema = workspace.draft.get(schema_key, {})
if not isinstance(target_schema, dict):
@@ -218,7 +230,9 @@ class WorkflowDraftAuthoringApi:
if source_root == "local" and target_root in {"state", "output"}:
local_field = _local_field(source_path)
output_schema = spec.output_schema_contract or spec.output_model.model_json_schema()
output_schema = (
spec.output_schema_contract or spec.output_model.model_json_schema()
)
schema_key = "state_schema" if target_root == "state" else "output_schema"
target_schema = workspace.draft.get(schema_key, {})
if not isinstance(target_schema, dict):
@@ -230,7 +244,9 @@ class WorkflowDraftAuthoringApi:
target_parts=target_parts,
)
output_map = {
**self.drafts._step_output_map(workspace_id=workspace_id, step_id=step_id),
**self.drafts._step_output_map(
workspace_id=workspace_id, step_id=step_id
),
local_field: target_path,
}
return await self.drafts.patch_draft_workspace(
@@ -246,7 +262,9 @@ class WorkflowDraftAuthoringApi:
],
)
raise ValueError(f"unsupported bind direction: {source_path!r} -> {target_path!r}")
raise ValueError(
f"unsupported bind direction: {source_path!r} -> {target_path!r}"
)
async def add_step_from_capability(
self,
+4 -1
View File
@@ -39,7 +39,10 @@ def input_bindings_payload(
def output_bindings_payload(output_map: dict[str, str]) -> list[dict[str, Any]]:
"""Serialize draft output maps into canonical string-path binding payloads."""
return [
{"source": _local_path_payload(source), "target": _graph_source_path_payload(target)}
{
"source": _local_path_payload(source),
"target": _graph_source_path_payload(target),
}
for source, target in output_map.items()
]
+2 -2
View File
@@ -473,6 +473,6 @@ def _draft_repair_hint(
if not isinstance(output_field, str) or not isinstance(state_path, str):
return None
return (
f"wf draft bind-output-to-state {workspace_id} --revision {revision} "
f"--step {step_id} --output {output_field} --state {state_path}"
f"wf draft bind {workspace_id} --revision {revision} "
f"--step {step_id} --from local.{output_field} --to {state_path}"
)
+18 -6
View File
@@ -31,17 +31,23 @@ def project_property_to_schema_path(
_ensure_object_schema(projected, "target_schema")
parent = projected
for index, part in enumerate(target_parts[:-1]):
properties = _properties_for_object(parent, ".".join(target_parts[:index]) or "target_schema")
properties = _properties_for_object(
parent, ".".join(target_parts[:index]) or "target_schema"
)
child = properties.get(part)
if child is None:
child = {"type": "object", "properties": {}}
properties[part] = child
if not isinstance(child, dict):
raise ValueError(f"schema path {'.'.join(target_parts[: index + 1])!r} is not an object")
raise ValueError(
f"schema path {'.'.join(target_parts[: index + 1])!r} is not an object"
)
_ensure_object_schema(child, ".".join(target_parts[: index + 1]))
parent = child
properties = _properties_for_object(parent, ".".join(target_parts[:-1]) or "target_schema")
properties = _properties_for_object(
parent, ".".join(target_parts[:-1]) or "target_schema"
)
leaf = target_parts[-1]
if leaf in properties:
raise ValueError(f"schema path {'.'.join(target_parts)!r} already exists")
@@ -76,15 +82,21 @@ def project_output_property_to_state_schema(
if msg.startswith("source field ") and "is not declared" in msg:
raise ValueError(f"output field {output_field!r} is not declared") from exc
if msg.startswith("source field ") and "not a JSON Schema" in msg:
raise ValueError(f"output field {output_field!r} is not a JSON Schema object") from exc
raise ValueError(
f"output field {output_field!r} is not a JSON Schema object"
) from exc
if "schema path 'target_schema'" in msg and "is not an object" in msg:
raise ValueError("state_schema must be an object schema") from exc
if msg.startswith("schema path ") and "already exists" in msg:
raise ValueError(f"state field {state_field!r} already exists") from exc
if "target_schema is not valid JSON Schema" in msg:
raise ValueError(f"state_schema is not valid JSON Schema: {msg.split(': ', 1)[1]}") from exc
raise ValueError(
f"state_schema is not valid JSON Schema: {msg.split(': ', 1)[1]}"
) from exc
if "source_schema is not valid JSON Schema" in msg:
raise ValueError(f"output_schema is not valid JSON Schema: {msg.split(': ', 1)[1]}") from exc
raise ValueError(
f"output_schema is not valid JSON Schema: {msg.split(': ', 1)[1]}"
) from exc
raise