@prompty/core 2.0.0-alpha.5 → 2.0.0-alpha.6

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/dist/index.js CHANGED
@@ -4499,6 +4499,9 @@ var init_common = __esm({
4499
4499
 
4500
4500
  // src/core/tool-dispatch.ts
4501
4501
  import { dirname as dirname2, resolve as resolve2 } from "path";
4502
+ function registerTool(name, handler) {
4503
+ nameRegistry.set(name, handler);
4504
+ }
4502
4505
  function getTool(name) {
4503
4506
  return nameRegistry.get(name);
4504
4507
  }
@@ -4522,12 +4525,12 @@ async function dispatchTool(toolName, args, userTools, agent, parentInputs) {
4522
4525
  const result = await registeredFn(args);
4523
4526
  return typeof result === "string" ? result : JSON.stringify(result);
4524
4527
  }
4525
- const tool = agent.tools?.find((t) => t.name === toolName);
4526
- if (!tool) {
4528
+ const tool2 = agent.tools?.find((t) => t.name === toolName);
4529
+ if (!tool2) {
4527
4530
  const available = Object.keys(userTools).sort().join(", ") || "(none)";
4528
4531
  return `Error: tool "${toolName}" not found in userTools or agent.tools. Available user tools: ${available}`;
4529
4532
  }
4530
- const kind = tool.kind || "*";
4533
+ const kind = tool2.kind || "*";
4531
4534
  let handler;
4532
4535
  try {
4533
4536
  handler = getToolHandler(kind);
@@ -4539,7 +4542,7 @@ async function dispatchTool(toolName, args, userTools, agent, parentInputs) {
4539
4542
  }
4540
4543
  }
4541
4544
  return await handler.executeTool(
4542
- tool,
4545
+ tool2,
4543
4546
  args,
4544
4547
  agent,
4545
4548
  parentInputs
@@ -4564,34 +4567,34 @@ var init_tool_dispatch = __esm({
4564
4567
  nameRegistry = /* @__PURE__ */ new Map();
4565
4568
  toolHandlers = /* @__PURE__ */ new Map();
4566
4569
  FunctionToolHandler = class {
4567
- async executeTool(tool, _args, _agent, _parentInputs) {
4568
- const name = tool.name ?? "unknown";
4570
+ async executeTool(tool2, _args, _agent, _parentInputs) {
4571
+ const name = tool2.name ?? "unknown";
4569
4572
  throw new Error(
4570
4573
  `Function tool '${name}' declared but no callable provided. Pass it via tools: { '${name}': fn } in invokeAgent().`
4571
4574
  );
4572
4575
  }
4573
4576
  };
4574
4577
  PromptyToolHandler = class {
4575
- async executeTool(tool, args, agent, _parentInputs) {
4578
+ async executeTool(tool2, args, agent, _parentInputs) {
4576
4579
  const { load: load2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
4577
4580
  const { prepare: prepare2, run: run2, invokeAgent: invokeAgent2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
4578
4581
  const parentPath = (agent.metadata ?? {}).__source_path;
4579
4582
  if (!parentPath) {
4580
- return `Error: cannot resolve PromptyTool '${tool.name}': parent has no __source_path`;
4583
+ return `Error: cannot resolve PromptyTool '${tool2.name}': parent has no __source_path`;
4581
4584
  }
4582
- const childPath = resolve2(dirname2(parentPath), tool.path);
4585
+ const childPath = resolve2(dirname2(parentPath), tool2.path);
4583
4586
  const stack = (agent.metadata ?? {}).__prompty_tool_stack ?? [];
4584
4587
  const normalizedChild = resolve2(childPath);
4585
4588
  const visited = /* @__PURE__ */ new Set([...stack.map((p) => resolve2(p)), resolve2(parentPath)]);
4586
4589
  if (visited.has(normalizedChild)) {
4587
4590
  const chain = [...stack, parentPath, childPath].join(" \u2192 ");
4588
- return `Error executing PromptyTool '${tool.name}': circular reference detected: ${chain}`;
4591
+ return `Error executing PromptyTool '${tool2.name}': circular reference detected: ${chain}`;
4589
4592
  }
4590
4593
  try {
4591
4594
  const child = load2(childPath);
4592
4595
  if (!child.metadata) child.metadata = {};
4593
4596
  child.metadata.__prompty_tool_stack = [...stack, parentPath];
4594
- const mode = tool.mode ?? "single";
4597
+ const mode = tool2.mode ?? "single";
4595
4598
  if (mode === "agentic") {
4596
4599
  const result = await invokeAgent2(child, args);
4597
4600
  return typeof result === "string" ? result : JSON.stringify(result);
@@ -4601,7 +4604,7 @@ var init_tool_dispatch = __esm({
4601
4604
  return typeof result === "string" ? result : JSON.stringify(result);
4602
4605
  }
4603
4606
  } catch (err) {
4604
- return `Error executing PromptyTool '${tool.name}': ${err instanceof Error ? err.message : String(err)}`;
4607
+ return `Error executing PromptyTool '${tool2.name}': ${err instanceof Error ? err.message : String(err)}`;
4605
4608
  }
4606
4609
  }
4607
4610
  };
@@ -4628,6 +4631,205 @@ var init_tool_dispatch = __esm({
4628
4631
  }
4629
4632
  });
4630
4633
 
4634
+ // src/core/agent-events.ts
4635
+ function emitEvent(callback, eventType, data) {
4636
+ if (!callback) return;
4637
+ try {
4638
+ callback(eventType, data);
4639
+ } catch (err) {
4640
+ if (typeof globalThis.console?.debug === "function") {
4641
+ globalThis.console.debug(`Event callback error for ${eventType}:`, err);
4642
+ }
4643
+ }
4644
+ }
4645
+ var init_agent_events = __esm({
4646
+ "src/core/agent-events.ts"() {
4647
+ "use strict";
4648
+ }
4649
+ });
4650
+
4651
+ // src/core/cancellation.ts
4652
+ function checkCancellation(signal) {
4653
+ if (signal?.aborted) {
4654
+ throw new CancelledError();
4655
+ }
4656
+ }
4657
+ var CancelledError;
4658
+ var init_cancellation = __esm({
4659
+ "src/core/cancellation.ts"() {
4660
+ "use strict";
4661
+ CancelledError = class extends Error {
4662
+ constructor(message = "Agent loop cancelled") {
4663
+ super(message);
4664
+ this.name = "CancelledError";
4665
+ }
4666
+ };
4667
+ }
4668
+ });
4669
+
4670
+ // src/core/context.ts
4671
+ function estimateChars(messages) {
4672
+ let total = 0;
4673
+ for (const msg of messages) {
4674
+ total += msg.role.length + 4;
4675
+ for (const part of msg.parts) {
4676
+ if (part.kind === "text") {
4677
+ total += part.value.length;
4678
+ } else {
4679
+ total += 200;
4680
+ }
4681
+ }
4682
+ const toolCalls = msg.metadata?.tool_calls;
4683
+ if (toolCalls) {
4684
+ total += JSON.stringify(toolCalls).length;
4685
+ }
4686
+ }
4687
+ return total;
4688
+ }
4689
+ function truncate(text2, maxLen = 200) {
4690
+ return text2.length <= maxLen ? text2 : text2.slice(0, maxLen) + "\u2026";
4691
+ }
4692
+ function summarizeDropped(messages) {
4693
+ const lines = [];
4694
+ for (const msg of messages) {
4695
+ const msgText = msg.text.trim();
4696
+ if (msg.role === "user" && msgText) {
4697
+ lines.push(`User asked: ${truncate(msgText)}`);
4698
+ } else if (msg.role === "assistant") {
4699
+ if (msgText) lines.push(`Assistant: ${truncate(msgText)}`);
4700
+ const toolCalls = msg.metadata?.tool_calls;
4701
+ if (Array.isArray(toolCalls)) {
4702
+ const names = toolCalls.map(
4703
+ (tc) => tc.name ?? (tc.function?.name ?? "?")
4704
+ );
4705
+ lines.push(` Called tools: ${names.join(", ")}`);
4706
+ }
4707
+ }
4708
+ }
4709
+ if (lines.length === 0) return "";
4710
+ let result = "[Context summary: ";
4711
+ for (const line of lines) {
4712
+ if (result.length + line.length > 4e3) {
4713
+ result += "\n... (older messages omitted)";
4714
+ break;
4715
+ }
4716
+ result += line + "\n";
4717
+ }
4718
+ return result.trimEnd() + "]";
4719
+ }
4720
+ function trimToContextWindow(messages, budgetChars) {
4721
+ if (estimateChars(messages) <= budgetChars) {
4722
+ return [0, []];
4723
+ }
4724
+ let systemEnd = 0;
4725
+ for (let i = 0; i < messages.length; i++) {
4726
+ if (messages[i].role !== "system") {
4727
+ systemEnd = i;
4728
+ break;
4729
+ }
4730
+ if (i === messages.length - 1) systemEnd = messages.length;
4731
+ }
4732
+ const systemMsgs = messages.slice(0, systemEnd);
4733
+ const rest = messages.slice(systemEnd);
4734
+ const summaryBudget = Math.min(5e3, Math.floor(budgetChars * 0.05));
4735
+ const dropped = [];
4736
+ while (estimateChars([...systemMsgs, ...rest]) > budgetChars - summaryBudget && rest.length > 2) {
4737
+ dropped.push(rest.shift());
4738
+ }
4739
+ const droppedCount = dropped.length;
4740
+ messages.length = 0;
4741
+ messages.push(...systemMsgs);
4742
+ if (droppedCount > 0) {
4743
+ const summaryText = summarizeDropped(dropped);
4744
+ if (summaryText) {
4745
+ messages.push(new Message("user", [{ kind: "text", value: summaryText }]));
4746
+ }
4747
+ }
4748
+ messages.push(...rest);
4749
+ return [droppedCount, dropped];
4750
+ }
4751
+ var init_context2 = __esm({
4752
+ "src/core/context.ts"() {
4753
+ "use strict";
4754
+ init_types();
4755
+ }
4756
+ });
4757
+
4758
+ // src/core/guardrails.ts
4759
+ var GuardrailError, Guardrails;
4760
+ var init_guardrails = __esm({
4761
+ "src/core/guardrails.ts"() {
4762
+ "use strict";
4763
+ GuardrailError = class extends Error {
4764
+ reason;
4765
+ constructor(reason) {
4766
+ super(`Guardrail denied: ${reason}`);
4767
+ this.name = "GuardrailError";
4768
+ this.reason = reason;
4769
+ }
4770
+ };
4771
+ Guardrails = class {
4772
+ inputHook;
4773
+ outputHook;
4774
+ toolHook;
4775
+ constructor(options) {
4776
+ this.inputHook = options?.input;
4777
+ this.outputHook = options?.output;
4778
+ this.toolHook = options?.tool;
4779
+ }
4780
+ checkInput(messages) {
4781
+ if (!this.inputHook) return { allowed: true };
4782
+ return this.inputHook(messages);
4783
+ }
4784
+ checkOutput(message) {
4785
+ if (!this.outputHook) return { allowed: true };
4786
+ return this.outputHook(message);
4787
+ }
4788
+ checkTool(name, args) {
4789
+ if (!this.toolHook) return { allowed: true };
4790
+ return this.toolHook(name, args);
4791
+ }
4792
+ };
4793
+ }
4794
+ });
4795
+
4796
+ // src/core/structured.ts
4797
+ function createStructuredResult(data, rawJson) {
4798
+ const result = { ...data };
4799
+ Object.defineProperty(result, StructuredResultSymbol, {
4800
+ value: rawJson,
4801
+ writable: false,
4802
+ enumerable: false,
4803
+ configurable: false
4804
+ });
4805
+ return result;
4806
+ }
4807
+ function isStructuredResult(value) {
4808
+ return typeof value === "object" && value !== null && StructuredResultSymbol in value;
4809
+ }
4810
+ function cast(result, validator) {
4811
+ let jsonStr;
4812
+ if (isStructuredResult(result)) {
4813
+ jsonStr = result[StructuredResultSymbol];
4814
+ } else if (typeof result === "string") {
4815
+ jsonStr = result;
4816
+ } else {
4817
+ jsonStr = JSON.stringify(result);
4818
+ }
4819
+ const parsed = JSON.parse(jsonStr);
4820
+ if (validator) {
4821
+ return validator(parsed);
4822
+ }
4823
+ return parsed;
4824
+ }
4825
+ var StructuredResultSymbol;
4826
+ var init_structured = __esm({
4827
+ "src/core/structured.ts"() {
4828
+ "use strict";
4829
+ StructuredResultSymbol = /* @__PURE__ */ Symbol("prompty.rawJson");
4830
+ }
4831
+ });
4832
+
4631
4833
  // src/core/pipeline.ts
4632
4834
  var pipeline_exports = {};
4633
4835
  __export(pipeline_exports, {
@@ -4831,6 +5033,9 @@ async function invoke(prompt, inputs, options) {
4831
5033
  const messages = await prepare(agent, inputs);
4832
5034
  const result = await run(agent, messages, options);
4833
5035
  emit("result", result);
5036
+ if (options?.validator) {
5037
+ return cast(result, options.validator);
5038
+ }
4834
5039
  return result;
4835
5040
  });
4836
5041
  }
@@ -4852,7 +5057,7 @@ function isAsyncIterable(value) {
4852
5057
  function isToolCallLike(item) {
4853
5058
  return typeof item === "object" && item !== null && "id" in item && "name" in item && "arguments" in item;
4854
5059
  }
4855
- async function consumeStream(agent, response) {
5060
+ async function consumeStream(agent, response, onEvent) {
4856
5061
  const processed = await process2(agent, response);
4857
5062
  const toolCalls = [];
4858
5063
  const textParts = [];
@@ -4862,96 +5067,17 @@ async function consumeStream(agent, response) {
4862
5067
  toolCalls.push(item);
4863
5068
  } else if (typeof item === "string") {
4864
5069
  textParts.push(item);
5070
+ emitEvent(onEvent, "token", { token: item });
4865
5071
  }
4866
5072
  }
4867
5073
  } else if (typeof processed === "string") {
4868
5074
  textParts.push(processed);
5075
+ emitEvent(onEvent, "token", { token: processed });
4869
5076
  }
4870
5077
  return { toolCalls, content: textParts.join("") };
4871
5078
  }
4872
- async function buildToolMessagesFromCalls(toolCalls, textContent, tools, agent, parentInputs, parentEmit) {
4873
- const provider = resolveProvider(agent);
4874
- const apiType = agent.model?.apiType || "chat";
4875
- const messages = [];
4876
- const toolInputs = [];
4877
- if (provider === "anthropic") {
4878
- const rawContent = [];
4879
- if (textContent) rawContent.push({ type: "text", text: textContent });
4880
- for (const tc of toolCalls) {
4881
- rawContent.push({
4882
- type: "tool_use",
4883
- id: tc.id,
4884
- name: tc.name,
4885
- input: JSON.parse(tc.arguments)
4886
- });
4887
- }
4888
- messages.push(
4889
- new Message("assistant", textContent ? [text(textContent)] : [], { content: rawContent })
4890
- );
4891
- } else if (apiType === "responses") {
4892
- for (const tc of toolCalls) {
4893
- messages.push(
4894
- new Message("assistant", [], {
4895
- responses_function_call: {
4896
- type: "function_call",
4897
- call_id: tc.id,
4898
- name: tc.name,
4899
- arguments: tc.arguments
4900
- }
4901
- })
4902
- );
4903
- }
4904
- } else {
4905
- const rawToolCalls = toolCalls.map((tc) => ({
4906
- id: tc.id,
4907
- type: "function",
4908
- function: { name: tc.name, arguments: tc.arguments }
4909
- }));
4910
- messages.push(
4911
- new Message("assistant", textContent ? [text(textContent)] : [], {
4912
- tool_calls: rawToolCalls
4913
- })
4914
- );
4915
- }
4916
- const toolResultBlocks = [];
4917
- for (const tc of toolCalls) {
4918
- let result;
4919
- let parsedArgs;
4920
- try {
4921
- parsedArgs = JSON.parse(tc.arguments);
4922
- if (parentInputs && typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)) {
4923
- parsedArgs = resolveBindings(agent, tc.name, parsedArgs, parentInputs);
4924
- }
4925
- result = await traceSpan(tc.name, async (toolEmit) => {
4926
- toolEmit("signature", `prompty.tool.${tc.name}`);
4927
- toolEmit("description", `Execute tool: ${tc.name}`);
4928
- toolEmit("inputs", { arguments: parsedArgs, id: tc.id });
4929
- const r = await dispatchTool(tc.name, parsedArgs, tools, agent, parentInputs ?? {});
4930
- toolEmit("result", r);
4931
- return r;
4932
- });
4933
- } catch (err) {
4934
- result = `Error: ${err instanceof Error ? err.message : String(err)}`;
4935
- }
4936
- toolInputs.push({ name: tc.name, arguments: parsedArgs, id: tc.id, result });
4937
- if (provider === "anthropic") {
4938
- toolResultBlocks.push({ type: "tool_result", tool_use_id: tc.id, content: result });
4939
- } else {
4940
- messages.push(
4941
- new Message("tool", [text(result)], { tool_call_id: tc.id, name: tc.name })
4942
- );
4943
- }
4944
- }
4945
- if (provider === "anthropic" && toolResultBlocks.length > 0) {
4946
- messages.push(new Message("user", [], { tool_results: toolResultBlocks }));
4947
- }
4948
- if (parentEmit) {
4949
- parentEmit("inputs", { tool_calls: toolInputs });
4950
- }
4951
- return messages;
4952
- }
4953
5079
  async function invokeAgent(prompt, inputs, options) {
4954
- return traceSpan("invokeAgent", async (emit) => {
5080
+ const rawResult = await traceSpan("invokeAgent", async (emit) => {
4955
5081
  const agent = typeof prompt === "string" ? await traceSpan("load", async (loadEmit) => {
4956
5082
  loadEmit("signature", "prompty.load");
4957
5083
  loadEmit("description", "Load a prompty file.");
@@ -4962,21 +5088,72 @@ async function invokeAgent(prompt, inputs, options) {
4962
5088
  }) : prompt;
4963
5089
  const tools = options?.tools ?? {};
4964
5090
  const maxIterations = options?.maxIterations ?? DEFAULT_MAX_ITERATIONS;
5091
+ const onEvent = options?.onEvent;
5092
+ const signal = options?.signal;
5093
+ const contextBudget = options?.contextBudget;
5094
+ const guardrails = options?.guardrails;
5095
+ const steering = options?.steering;
5096
+ const parallelToolCalls = options?.parallelToolCalls ?? false;
4965
5097
  emit("signature", "prompty.invokeAgent");
4966
5098
  emit("description", "Invoke a prompty with tool calling");
4967
5099
  emit("inputs", { prompt: serializeAgent(agent), tools: Object.keys(tools), inputs: inputs ?? {} });
4968
- const messages = await prepare(agent, inputs);
5100
+ let messages = await prepare(agent, inputs);
4969
5101
  const parentInputs = inputs ?? {};
4970
5102
  const provider = resolveProvider(agent);
4971
5103
  const executor = getExecutor(provider);
4972
- let response = await executor.execute(agent, messages);
5104
+ let response = null;
4973
5105
  let iteration = 0;
4974
5106
  while (true) {
5107
+ try {
5108
+ checkCancellation(signal);
5109
+ } catch (err) {
5110
+ emitEvent(onEvent, "cancelled", {});
5111
+ throw err;
5112
+ }
5113
+ if (steering) {
5114
+ const pending = steering.drain();
5115
+ if (pending.length > 0) {
5116
+ messages.push(...pending);
5117
+ emitEvent(onEvent, "messages_updated", { messages });
5118
+ emitEvent(onEvent, "status", { message: `Injected ${pending.length} steering message(s)` });
5119
+ }
5120
+ }
5121
+ if (contextBudget !== void 0) {
5122
+ const [droppedCount] = trimToContextWindow(messages, contextBudget);
5123
+ if (droppedCount > 0) {
5124
+ emitEvent(onEvent, "messages_updated", { messages });
5125
+ emitEvent(onEvent, "status", { message: `Trimmed ${droppedCount} messages for context budget` });
5126
+ }
5127
+ }
5128
+ if (guardrails) {
5129
+ const result2 = guardrails.checkInput(messages);
5130
+ if (!result2.allowed) {
5131
+ emitEvent(onEvent, "error", { message: `Input guardrail denied: ${result2.reason}` });
5132
+ throw new GuardrailError(result2.reason ?? "Input guardrail denied");
5133
+ }
5134
+ if (result2.rewrite) messages = result2.rewrite;
5135
+ }
5136
+ try {
5137
+ checkCancellation(signal);
5138
+ } catch (err) {
5139
+ emitEvent(onEvent, "cancelled", {});
5140
+ throw err;
5141
+ }
5142
+ response = await executor.execute(agent, messages);
4975
5143
  if (isAsyncIterable(response)) {
4976
- const { toolCalls, content } = await consumeStream(agent, response);
5144
+ const { toolCalls, content } = await consumeStream(agent, response, onEvent);
5145
+ if (guardrails && content) {
5146
+ const assistantMsg = new Message("assistant", [text(content)]);
5147
+ const gr = guardrails.checkOutput(assistantMsg);
5148
+ if (!gr.allowed) {
5149
+ emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` });
5150
+ throw new GuardrailError(gr.reason ?? "Output guardrail denied");
5151
+ }
5152
+ }
4977
5153
  if (toolCalls.length === 0) {
4978
5154
  emit("iterations", iteration);
4979
5155
  emit("result", content);
5156
+ emitEvent(onEvent, "done", { response: content, messages });
4980
5157
  return content;
4981
5158
  }
4982
5159
  iteration++;
@@ -4988,15 +5165,55 @@ async function invokeAgent(prompt, inputs, options) {
4988
5165
  const toolMessages2 = await traceSpan("toolCalls", async (toolEmit) => {
4989
5166
  toolEmit("signature", "prompty.invokeAgent.toolCalls");
4990
5167
  toolEmit("description", `Tool call round ${iteration}`);
4991
- const result2 = await buildToolMessagesFromCalls(toolCalls, content, tools, agent, parentInputs, toolEmit);
5168
+ const result2 = await buildToolMessagesFromCallsWithExtensions(
5169
+ toolCalls,
5170
+ content,
5171
+ tools,
5172
+ agent,
5173
+ parentInputs,
5174
+ toolEmit,
5175
+ { onEvent, signal, guardrails, parallel: parallelToolCalls }
5176
+ );
4992
5177
  toolEmit("result", result2.map((m) => ({ role: m.role, content: m.parts.map((p) => p.value ?? "").join(""), metadata: m.metadata })));
4993
5178
  return result2;
4994
5179
  });
4995
5180
  messages.push(...toolMessages2);
4996
- response = await executor.execute(agent, messages);
5181
+ emitEvent(onEvent, "messages_updated", { messages });
4997
5182
  continue;
4998
5183
  }
4999
- if (!hasToolCalls(response)) break;
5184
+ if (!hasToolCalls(response)) {
5185
+ const finalResult = options?.raw ? response : await process2(agent, response);
5186
+ if (guardrails) {
5187
+ const contentStr = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult);
5188
+ const assistantMsg = new Message("assistant", [text(contentStr)]);
5189
+ const gr = guardrails.checkOutput(assistantMsg);
5190
+ if (!gr.allowed) {
5191
+ emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` });
5192
+ throw new GuardrailError(gr.reason ?? "Output guardrail denied");
5193
+ }
5194
+ if (gr.rewrite !== void 0) {
5195
+ emit("iterations", iteration);
5196
+ emit("result", gr.rewrite);
5197
+ emitEvent(onEvent, "done", { response: gr.rewrite, messages });
5198
+ return gr.rewrite;
5199
+ }
5200
+ }
5201
+ emit("iterations", iteration);
5202
+ emit("result", finalResult);
5203
+ emitEvent(onEvent, "done", { response: finalResult, messages });
5204
+ return finalResult;
5205
+ }
5206
+ if (guardrails) {
5207
+ const { textContent } = extractToolInfo(response);
5208
+ if (textContent) {
5209
+ const assistantMsg = new Message("assistant", [text(textContent)]);
5210
+ const gr = guardrails.checkOutput(assistantMsg);
5211
+ if (!gr.allowed) {
5212
+ emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` });
5213
+ throw new GuardrailError(gr.reason ?? "Output guardrail denied");
5214
+ }
5215
+ }
5216
+ }
5000
5217
  iteration++;
5001
5218
  if (iteration > maxIterations) {
5002
5219
  throw new Error(
@@ -5006,22 +5223,35 @@ async function invokeAgent(prompt, inputs, options) {
5006
5223
  const toolMessages = await traceSpan("toolCalls", async (toolEmit) => {
5007
5224
  toolEmit("signature", "prompty.invokeAgent.toolCalls");
5008
5225
  toolEmit("description", `Tool call round ${iteration}`);
5009
- const result2 = await buildToolResultMessages(response, tools, agent, parentInputs, toolEmit);
5226
+ const result2 = await buildToolResultMessagesWithExtensions(
5227
+ response,
5228
+ tools,
5229
+ agent,
5230
+ parentInputs,
5231
+ toolEmit,
5232
+ { onEvent, signal, guardrails, parallel: parallelToolCalls }
5233
+ );
5010
5234
  toolEmit("result", result2.map((m) => ({ role: m.role, content: m.parts.map((p) => p.value ?? "").join(""), metadata: m.metadata })));
5011
5235
  return result2;
5012
5236
  });
5013
5237
  messages.push(...toolMessages);
5014
- response = await executor.execute(agent, messages);
5238
+ emitEvent(onEvent, "messages_updated", { messages });
5015
5239
  }
5016
5240
  emit("iterations", iteration);
5017
5241
  if (options?.raw) {
5018
5242
  emit("result", response);
5243
+ emitEvent(onEvent, "done", { response, messages });
5019
5244
  return response;
5020
5245
  }
5021
5246
  const result = await process2(agent, response);
5022
5247
  emit("result", result);
5248
+ emitEvent(onEvent, "done", { response: result, messages });
5023
5249
  return result;
5024
5250
  });
5251
+ if (options?.validator) {
5252
+ return cast(rawResult, options.validator);
5253
+ }
5254
+ return rawResult;
5025
5255
  }
5026
5256
  function expandThreads(messages, nonces, inputs) {
5027
5257
  if (nonces.size === 0) return messages;
@@ -5090,158 +5320,136 @@ function hasToolCalls(response) {
5090
5320
  }
5091
5321
  return false;
5092
5322
  }
5093
- async function buildToolResultMessages(response, tools, agent, parentInputs, parentEmit) {
5323
+ function extractToolInfo(response) {
5324
+ if (typeof response !== "object" || response === null) {
5325
+ return { toolCalls: [], textContent: "" };
5326
+ }
5094
5327
  const r = response;
5095
5328
  if (Array.isArray(r.content) && r.stop_reason === "tool_use") {
5096
- return buildAnthropicToolResultMessages(r, tools, agent, parentInputs, parentEmit);
5329
+ const content = r.content;
5330
+ const toolCalls = content.filter((b) => b.type === "tool_use").map((b) => ({
5331
+ id: b.id,
5332
+ name: b.name,
5333
+ arguments: JSON.stringify(b.input)
5334
+ }));
5335
+ const textContent = content.filter((b) => b.type === "text").map((b) => b.text).join("");
5336
+ return { toolCalls, textContent };
5097
5337
  }
5098
5338
  if (r.object === "response" && Array.isArray(r.output)) {
5099
- return buildResponsesToolResultMessages(r, tools, agent, parentInputs, parentEmit);
5339
+ const funcCalls = r.output.filter(
5340
+ (item) => item.type === "function_call"
5341
+ );
5342
+ const toolCalls = funcCalls.map((fc) => ({
5343
+ id: fc.call_id ?? fc.id ?? "",
5344
+ call_id: fc.call_id ?? fc.id ?? "",
5345
+ name: fc.name,
5346
+ arguments: fc.arguments ?? "{}"
5347
+ }));
5348
+ return { toolCalls, textContent: "" };
5100
5349
  }
5101
- return buildOpenAIToolResultMessages(r, tools, agent, parentInputs, parentEmit);
5102
- }
5103
- async function buildOpenAIToolResultMessages(r, tools, agent, parentInputs, parentEmit) {
5104
5350
  const choices = r.choices;
5105
- const choice = choices[0];
5106
- const message = choice.message;
5107
- const toolCalls = message.tool_calls;
5108
- const messages = [];
5109
- const assistantContent = message.content ?? "";
5110
- messages.push(
5111
- new Message("assistant", assistantContent ? [text(assistantContent)] : [], {
5112
- tool_calls: toolCalls
5113
- })
5114
- );
5115
- const toolInputs = [];
5116
- for (const tc of toolCalls) {
5117
- const fn = tc.function;
5118
- const toolName = fn.name;
5119
- const toolCallId = tc.id;
5120
- let result;
5121
- let parsedArgs;
5122
- try {
5123
- parsedArgs = JSON.parse(fn.arguments);
5124
- if (agent && parentInputs && typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)) {
5125
- parsedArgs = resolveBindings(agent, toolName, parsedArgs, parentInputs);
5126
- }
5127
- result = await traceSpan(toolName, async (toolEmit) => {
5128
- toolEmit("signature", `prompty.tool.${toolName}`);
5129
- toolEmit("description", `Execute tool: ${toolName}`);
5130
- toolEmit("inputs", { arguments: parsedArgs, tool_call_id: toolCallId });
5131
- const r2 = await dispatchTool(toolName, parsedArgs, tools, agent ?? {}, parentInputs ?? {});
5132
- toolEmit("result", r2);
5133
- return r2;
5351
+ if (Array.isArray(choices) && choices.length > 0) {
5352
+ const choice = choices[0];
5353
+ const message = choice.message;
5354
+ if (message && Array.isArray(message.tool_calls)) {
5355
+ const toolCalls = message.tool_calls.map((tc) => {
5356
+ const fn = tc.function;
5357
+ return {
5358
+ id: tc.id,
5359
+ name: fn.name,
5360
+ arguments: fn.arguments
5361
+ };
5134
5362
  });
5135
- } catch (err) {
5136
- result = `Error: ${err instanceof Error ? err.message : String(err)}`;
5363
+ return { toolCalls, textContent: message.content ?? "" };
5137
5364
  }
5138
- toolInputs.push({ name: toolName, arguments: parsedArgs, tool_call_id: toolCallId, result });
5139
- messages.push(
5140
- new Message("tool", [text(result)], {
5141
- tool_call_id: toolCallId,
5142
- name: toolName
5143
- })
5144
- );
5145
- }
5146
- if (parentEmit) {
5147
- parentEmit("inputs", { tool_calls: toolInputs });
5148
5365
  }
5149
- return messages;
5366
+ return { toolCalls: [], textContent: "" };
5150
5367
  }
5151
- async function buildAnthropicToolResultMessages(r, tools, agent, parentInputs, parentEmit) {
5152
- const content = r.content;
5153
- const toolUseBlocks = content.filter((block) => block.type === "tool_use");
5154
- const messages = [];
5155
- const textParts = content.filter((block) => block.type === "text").map((block) => text(block.text));
5156
- messages.push(
5157
- new Message("assistant", textParts, { content })
5158
- );
5159
- const toolInputs = [];
5160
- const toolResultBlocks = [];
5161
- for (const block of toolUseBlocks) {
5162
- const toolName = block.name;
5163
- const toolCallId = block.id;
5164
- let toolArgs = block.input;
5165
- if (agent && parentInputs && typeof toolArgs === "object" && toolArgs !== null && !Array.isArray(toolArgs)) {
5166
- toolArgs = resolveBindings(agent, toolName, toolArgs, parentInputs);
5167
- }
5168
- let result;
5368
+ async function dispatchOneToolWithExtensions(tc, tools, agent, parentInputs, ext) {
5369
+ const { onEvent, signal, guardrails } = ext;
5370
+ try {
5371
+ checkCancellation(signal);
5372
+ } catch (err) {
5373
+ emitEvent(onEvent, "cancelled", {});
5374
+ throw err;
5375
+ }
5376
+ emitEvent(onEvent, "tool_call_start", { name: tc.name, arguments: tc.arguments });
5377
+ if (guardrails) {
5378
+ let parsedArgs2 = {};
5169
5379
  try {
5170
- result = await traceSpan(toolName, async (toolEmit) => {
5171
- toolEmit("signature", `prompty.tool.${toolName}`);
5172
- toolEmit("description", `Execute tool: ${toolName}`);
5173
- toolEmit("inputs", { arguments: toolArgs, tool_use_id: toolCallId });
5174
- const r2 = await dispatchTool(toolName, toolArgs, tools, agent ?? {}, parentInputs ?? {});
5175
- toolEmit("result", r2);
5176
- return r2;
5177
- });
5178
- } catch (err) {
5179
- result = `Error: ${err instanceof Error ? err.message : String(err)}`;
5380
+ const parsed = JSON.parse(tc.arguments);
5381
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
5382
+ parsedArgs2 = parsed;
5383
+ }
5384
+ } catch {
5385
+ }
5386
+ const gr = guardrails.checkTool(tc.name, parsedArgs2);
5387
+ if (!gr.allowed) {
5388
+ const deniedMsg = `Tool denied by guardrail: ${gr.reason}`;
5389
+ emitEvent(onEvent, "tool_result", { name: tc.name, result: deniedMsg });
5390
+ return deniedMsg;
5391
+ }
5392
+ if (gr.rewrite !== void 0) {
5393
+ tc = { ...tc, arguments: typeof gr.rewrite === "string" ? gr.rewrite : JSON.stringify(gr.rewrite) };
5180
5394
  }
5181
- toolInputs.push({ name: toolName, arguments: toolArgs, tool_use_id: toolCallId, result });
5182
- toolResultBlocks.push({
5183
- type: "tool_result",
5184
- tool_use_id: toolCallId,
5185
- content: result
5186
- });
5187
5395
  }
5188
- if (parentEmit) {
5189
- parentEmit("inputs", { tool_calls: toolInputs });
5396
+ let result;
5397
+ let parsedArgs;
5398
+ try {
5399
+ parsedArgs = JSON.parse(tc.arguments);
5400
+ if (agent && parentInputs && typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)) {
5401
+ parsedArgs = resolveBindings(agent, tc.name, parsedArgs, parentInputs);
5402
+ }
5403
+ result = await traceSpan(tc.name, async (toolEmit) => {
5404
+ toolEmit("signature", `prompty.tool.${tc.name}`);
5405
+ toolEmit("description", `Execute tool: ${tc.name}`);
5406
+ toolEmit("inputs", { arguments: parsedArgs, id: tc.id });
5407
+ const r = await dispatchTool(tc.name, parsedArgs, tools, agent, parentInputs);
5408
+ toolEmit("result", r);
5409
+ return r;
5410
+ });
5411
+ } catch (err) {
5412
+ if (err instanceof CancelledError) throw err;
5413
+ result = `Error: ${err instanceof Error ? err.message : String(err)}`;
5190
5414
  }
5191
- messages.push(
5192
- new Message("user", [], { tool_results: toolResultBlocks })
5193
- );
5194
- return messages;
5415
+ emitEvent(onEvent, "tool_result", { name: tc.name, result });
5416
+ return result;
5195
5417
  }
5196
- async function buildResponsesToolResultMessages(r, tools, agent, parentInputs, parentEmit) {
5197
- const output = r.output;
5198
- const funcCalls = output.filter((item) => item.type === "function_call");
5199
- const messages = [];
5200
- const toolInputs = [];
5201
- for (const fc of funcCalls) {
5202
- const toolName = fc.name;
5203
- const callId = fc.call_id ?? fc.id ?? "";
5204
- const argsStr = fc.arguments ?? "{}";
5205
- messages.push(
5206
- new Message("assistant", [], {
5207
- responses_function_call: {
5208
- type: "function_call",
5209
- call_id: callId,
5210
- name: toolName,
5211
- arguments: argsStr
5212
- }
5213
- })
5214
- );
5215
- let result;
5216
- let parsedArgs;
5217
- try {
5218
- parsedArgs = JSON.parse(argsStr);
5219
- if (agent && parentInputs && typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)) {
5220
- parsedArgs = resolveBindings(agent, toolName, parsedArgs, parentInputs);
5221
- }
5222
- result = await traceSpan(toolName, async (toolEmit) => {
5223
- toolEmit("signature", `prompty.tool.${toolName}`);
5224
- toolEmit("description", `Execute tool: ${toolName}`);
5225
- toolEmit("inputs", { arguments: parsedArgs, call_id: callId });
5226
- const r2 = await dispatchTool(toolName, parsedArgs, tools, agent ?? {}, parentInputs ?? {});
5227
- toolEmit("result", r2);
5228
- return r2;
5229
- });
5230
- } catch (err) {
5231
- result = `Error: ${err instanceof Error ? err.message : String(err)}`;
5232
- }
5233
- toolInputs.push({ name: toolName, arguments: parsedArgs, call_id: callId, result });
5234
- messages.push(
5235
- new Message("tool", [text(result)], {
5236
- tool_call_id: callId,
5237
- name: toolName
5238
- })
5418
+ async function dispatchToolsWithExtensions(toolCalls, tools, agent, parentInputs, ext) {
5419
+ if (ext.parallel && toolCalls.length > 1) {
5420
+ return Promise.all(
5421
+ toolCalls.map((tc) => dispatchOneToolWithExtensions(tc, tools, agent, parentInputs, ext))
5239
5422
  );
5240
5423
  }
5424
+ const results = [];
5425
+ for (const tc of toolCalls) {
5426
+ results.push(await dispatchOneToolWithExtensions(tc, tools, agent, parentInputs, ext));
5427
+ }
5428
+ return results;
5429
+ }
5430
+ async function buildToolResultMessagesWithExtensions(response, tools, agent, parentInputs, parentEmit, ext) {
5431
+ const { toolCalls, textContent } = extractToolInfo(response);
5432
+ const toolResults = await dispatchToolsWithExtensions(toolCalls, tools, agent, parentInputs, ext);
5433
+ if (parentEmit) {
5434
+ parentEmit("inputs", {
5435
+ tool_calls: toolCalls.map((tc, i) => ({ name: tc.name, arguments: tc.arguments, id: tc.id, result: toolResults[i] }))
5436
+ });
5437
+ }
5438
+ const provider = resolveProvider(agent);
5439
+ const executor = getExecutor(provider);
5440
+ return executor.formatToolMessages(response, toolCalls, toolResults, textContent);
5441
+ }
5442
+ async function buildToolMessagesFromCallsWithExtensions(toolCalls, textContent, tools, agent, parentInputs, parentEmit, ext) {
5443
+ const normalizedCalls = toolCalls.map((tc) => ({ id: tc.id, name: tc.name, arguments: tc.arguments }));
5444
+ const toolResults = await dispatchToolsWithExtensions(normalizedCalls, tools, agent, parentInputs, ext);
5241
5445
  if (parentEmit) {
5242
- parentEmit("inputs", { tool_calls: toolInputs });
5446
+ parentEmit("inputs", {
5447
+ tool_calls: normalizedCalls.map((tc, i) => ({ name: tc.name, arguments: tc.arguments, id: tc.id, result: toolResults[i] }))
5448
+ });
5243
5449
  }
5244
- return messages;
5450
+ const provider = resolveProvider(agent);
5451
+ const executor = getExecutor(provider);
5452
+ return executor.formatToolMessages(null, normalizedCalls, toolResults, textContent);
5245
5453
  }
5246
5454
  var DEFAULT_FORMAT, DEFAULT_PARSER, DEFAULT_PROVIDER, DEFAULT_MAX_ITERATIONS;
5247
5455
  var init_pipeline = __esm({
@@ -5253,6 +5461,11 @@ var init_pipeline = __esm({
5253
5461
  init_tracer();
5254
5462
  init_loader();
5255
5463
  init_tool_dispatch();
5464
+ init_agent_events();
5465
+ init_cancellation();
5466
+ init_context2();
5467
+ init_guardrails();
5468
+ init_structured();
5256
5469
  DEFAULT_FORMAT = "nunjucks";
5257
5470
  DEFAULT_PARSER = "prompty";
5258
5471
  DEFAULT_PROVIDER = "openai";
@@ -5286,6 +5499,101 @@ function clearConnections() {
5286
5499
  init_loader();
5287
5500
  init_pipeline();
5288
5501
  init_tool_dispatch();
5502
+ init_agent_events();
5503
+ init_cancellation();
5504
+ init_context2();
5505
+ init_guardrails();
5506
+
5507
+ // src/core/steering.ts
5508
+ init_types();
5509
+ var Steering = class {
5510
+ queue = [];
5511
+ /** Enqueue a message to be injected at the next iteration. */
5512
+ send(message) {
5513
+ this.queue.push(message);
5514
+ }
5515
+ /** Remove and return all queued messages as Message objects. */
5516
+ drain() {
5517
+ const items = this.queue.splice(0);
5518
+ return items.map((text2) => new Message("user", [{ kind: "text", value: text2 }]));
5519
+ }
5520
+ /** Whether there are pending messages without consuming them. */
5521
+ get hasPending() {
5522
+ return this.queue.length > 0;
5523
+ }
5524
+ };
5525
+
5526
+ // src/core/tool-decorator.ts
5527
+ init_tool();
5528
+ init_property();
5529
+ init_tool_dispatch();
5530
+ function tool(fn, options) {
5531
+ const toolName = options?.name ?? fn.name;
5532
+ const toolDesc = options?.description ?? "";
5533
+ const shouldRegister = options?.register !== false;
5534
+ const properties = (options?.parameters ?? []).map(
5535
+ (p) => new Property({
5536
+ name: p.name,
5537
+ kind: p.kind ?? "string",
5538
+ required: p.required ?? p.default === void 0,
5539
+ description: p.description,
5540
+ default: p.default
5541
+ })
5542
+ );
5543
+ const toolDef = new FunctionTool({
5544
+ name: toolName,
5545
+ kind: "function",
5546
+ description: toolDesc,
5547
+ parameters: properties
5548
+ });
5549
+ const wrapped = fn;
5550
+ wrapped.__tool__ = toolDef;
5551
+ if (shouldRegister) {
5552
+ registerTool(toolName, fn);
5553
+ }
5554
+ return wrapped;
5555
+ }
5556
+ function bindTools(agent, tools) {
5557
+ const handlers = {};
5558
+ for (const fn of tools) {
5559
+ const toolDef = fn.__tool__;
5560
+ if (!toolDef) {
5561
+ throw new Error(
5562
+ `Function '${fn.name || "(anonymous)"}' is not a tool()-wrapped function (missing __tool__ property)`
5563
+ );
5564
+ }
5565
+ const name = toolDef.name;
5566
+ if (name in handlers) {
5567
+ throw new Error(`Duplicate tool handler: '${name}'`);
5568
+ }
5569
+ handlers[name] = fn;
5570
+ }
5571
+ const declaredFunctionTools = /* @__PURE__ */ new Set();
5572
+ for (const toolDef of agent.tools ?? []) {
5573
+ if (toolDef.kind === "function") {
5574
+ declaredFunctionTools.add(toolDef.name);
5575
+ }
5576
+ }
5577
+ for (const name of Object.keys(handlers)) {
5578
+ if (!declaredFunctionTools.has(name)) {
5579
+ const declared = [...declaredFunctionTools].sort().join(", ") || "(none)";
5580
+ throw new Error(
5581
+ `Tool handler '${name}' has no matching 'kind: function' declaration in agent.tools. Declared function tools: ${declared}`
5582
+ );
5583
+ }
5584
+ }
5585
+ for (const name of declaredFunctionTools) {
5586
+ if (!(name in handlers)) {
5587
+ console.warn(
5588
+ `Tool '${name}' is declared in agent.tools but no handler was provided to bindTools()`
5589
+ );
5590
+ }
5591
+ }
5592
+ return handlers;
5593
+ }
5594
+
5595
+ // src/core/index.ts
5596
+ init_structured();
5289
5597
 
5290
5598
  // src/renderers/nunjucks.ts
5291
5599
  init_common();
@@ -5678,11 +5986,14 @@ export {
5678
5986
  ApiKeyConnection,
5679
5987
  ArrayProperty,
5680
5988
  Binding,
5989
+ CancelledError,
5681
5990
  Connection,
5682
5991
  CustomTool,
5683
5992
  FormatConfig,
5684
5993
  FoundryConnection,
5685
5994
  FunctionTool,
5995
+ GuardrailError,
5996
+ Guardrails,
5686
5997
  InvokerError,
5687
5998
  LoadContext,
5688
5999
  McpApprovalMode,
@@ -5708,15 +6019,23 @@ export {
5708
6019
  ReferenceConnection,
5709
6020
  RemoteConnection,
5710
6021
  SaveContext,
6022
+ Steering,
6023
+ StructuredResultSymbol,
5711
6024
  Template,
5712
6025
  ThreadMarker,
5713
6026
  Tool,
5714
6027
  Tracer,
6028
+ bindTools,
6029
+ cast,
6030
+ checkCancellation,
5715
6031
  clearCache,
5716
6032
  clearConnections,
5717
6033
  consoleTracer,
6034
+ createStructuredResult,
5718
6035
  dictContentToPart,
5719
6036
  dictToMessage,
6037
+ emitEvent,
6038
+ estimateChars,
5720
6039
  getConnection,
5721
6040
  getExecutor,
5722
6041
  getParser,
@@ -5724,6 +6043,7 @@ export {
5724
6043
  getRenderer,
5725
6044
  invoke,
5726
6045
  invokeAgent,
6046
+ isStructuredResult,
5727
6047
  load,
5728
6048
  otelTracer,
5729
6049
  parse,
@@ -5738,12 +6058,15 @@ export {
5738
6058
  resolveBindings,
5739
6059
  run,
5740
6060
  sanitizeValue,
6061
+ summarizeDropped,
5741
6062
  text,
5742
6063
  textMessage,
5743
6064
  toSerializable,
6065
+ tool,
5744
6066
  trace,
5745
6067
  traceMethod,
5746
6068
  traceSpan,
6069
+ trimToContextWindow,
5747
6070
  validateInputs
5748
6071
  };
5749
6072
  //# sourceMappingURL=index.js.map