orchajs 0.6.2 → 0.7.1

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.
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { OrchaError } from "../errors.js";
3
+ import { normalizeLocalFile, normalizeUrlContent, } from "../file-content.js";
3
4
  import { messagesFromEvents, normalizePersistedToolResults, SessionEventWriter, } from "./session-events.js";
4
5
  import { validateStructuredValue } from "./structured-output.js";
5
6
  import { executeLocalAction } from "./actions/execute.js";
@@ -25,6 +26,7 @@ export class RuntimeAgent {
25
26
  #delegatedOnly;
26
27
  #agentKey;
27
28
  #sessionIdPrefix;
29
+ #projectRoot;
28
30
  #evaluationTasks = new Map();
29
31
  #sessionIdleWaiters = new Map();
30
32
  constructor(options) {
@@ -38,6 +40,7 @@ export class RuntimeAgent {
38
40
  this.#delegatedOnly = options.delegatedOnly ?? false;
39
41
  this.#agentKey = options.manifest.key ?? options.manifest.name;
40
42
  this.#sessionIdPrefix = options.sessionIdPrefix ?? "ses_";
43
+ this.#projectRoot = options.projectRoot ?? process.cwd();
41
44
  }
42
45
  get agent() {
43
46
  return this.#agentKey;
@@ -379,7 +382,13 @@ export class RuntimeAgent {
379
382
  return [...events, ...recoveryEvents];
380
383
  }
381
384
  async #start(sessionId, input, createSession = true, publishOutput, publishSnapshot, lineage) {
382
- const content = normalizeContent(input.content);
385
+ let content;
386
+ try {
387
+ content = normalizeContent(input.content, this.#projectRoot);
388
+ }
389
+ catch (error) {
390
+ return failure(sessionId, "invalid_input", error instanceof Error ? error.message : String(error));
391
+ }
383
392
  if (!content) {
384
393
  return failure(sessionId, "invalid_input", "Agent content must contain at least one valid item.");
385
394
  }
@@ -1040,8 +1049,8 @@ export class RuntimeAgent {
1040
1049
  description: "The registered subagent to run.",
1041
1050
  },
1042
1051
  input: {
1043
- type: "string",
1044
- description: "The complete task and relevant context for the subagent.",
1052
+ ...agentContentInputSchema(),
1053
+ description: "The complete text or multimodal content for the subagent. Local file paths resolve from the configured Orcha project root.",
1045
1054
  },
1046
1055
  },
1047
1056
  required: ["agent", "input"],
@@ -1123,15 +1132,16 @@ export class RuntimeAgent {
1123
1132
  async #executeSubagentToolCall(parentSessionId, writer, call, events, publishSnapshot) {
1124
1133
  if (call.name === RUN_AGENT_TOOL_NAME) {
1125
1134
  const agentName = requiredToolString(call.arguments.agent, "agent");
1126
- const input = requiredToolString(call.arguments.input, "input");
1127
1135
  const child = this.#subagents[agentName];
1128
1136
  if (!child) {
1129
1137
  throw new Error(`Unknown subagent "${agentName}".`);
1130
1138
  }
1131
- const existing = previousSubagentCall(await this.#readSession(parentSessionId), call.callId);
1139
+ const parentEvents = await this.#readSession(parentSessionId);
1140
+ const existing = previousSubagentCall(parentEvents, call.callId);
1132
1141
  if (existing) {
1133
1142
  return subagentToolOutput(existing);
1134
1143
  }
1144
+ const input = normalizeDelegatedInput(call.arguments.input, this.#projectRoot);
1135
1145
  const lineage = {
1136
1146
  origin: "delegated",
1137
1147
  parentAgent: this.#manifest.key,
@@ -1147,7 +1157,7 @@ export class RuntimeAgent {
1147
1157
  });
1148
1158
  await this.#store.append(parentSessionId, [initiated]);
1149
1159
  events.push(initiated);
1150
- const childExecution = child.runAsChild(childSessionId, input, lineage);
1160
+ const childExecution = child.runAsChild(childSessionId, { content: input }, lineage);
1151
1161
  publishSnapshot?.({
1152
1162
  sessionId: parentSessionId,
1153
1163
  status: "waiting_for_subagent",
@@ -1621,24 +1631,49 @@ function isEvaluationMetricResult(value) {
1621
1631
  Array.isArray(value.evidence) &&
1622
1632
  value.evidence.every((item) => typeof item === "string"));
1623
1633
  }
1624
- function normalizeContent(content) {
1634
+ function normalizeContent(content, projectRoot) {
1625
1635
  if (typeof content === "string") {
1626
1636
  return content.trim()
1627
1637
  ? [{ type: "text", text: content }]
1628
1638
  : undefined;
1629
1639
  }
1630
- if (!Array.isArray(content) || content.length === 0) {
1640
+ const items = Array.isArray(content)
1641
+ ? content
1642
+ : [content];
1643
+ if (items.length === 0) {
1631
1644
  return undefined;
1632
1645
  }
1633
1646
  const normalized = [];
1634
- for (const item of content) {
1635
- if (item?.type === "text" &&
1647
+ for (const item of items) {
1648
+ if (item &&
1649
+ "type" in item &&
1650
+ item.type === "text" &&
1636
1651
  typeof item.text === "string" &&
1637
1652
  item.text.trim()) {
1638
1653
  normalized.push({ type: "text", text: item.text });
1639
1654
  continue;
1640
1655
  }
1641
1656
  if (item &&
1657
+ "filePath" in item &&
1658
+ typeof item.filePath === "string" &&
1659
+ item.filePath.trim()) {
1660
+ normalized.push(normalizeLocalFile(item.filePath, "mimeType" in item && typeof item.mimeType === "string"
1661
+ ? item.mimeType
1662
+ : undefined, projectRoot));
1663
+ continue;
1664
+ }
1665
+ if (item &&
1666
+ "url" in item &&
1667
+ typeof item.url === "string" &&
1668
+ item.url.trim()) {
1669
+ normalized.push(normalizeUrlContent(item.url, "mimeType" in item && typeof item.mimeType === "string"
1670
+ ? item.mimeType
1671
+ : undefined));
1672
+ continue;
1673
+ }
1674
+ if (item &&
1675
+ "type" in item &&
1676
+ typeof item.type === "string" &&
1642
1677
  ["image", "video", "audio", "url"].includes(item.type) &&
1643
1678
  "mimeType" in item &&
1644
1679
  "fileUri" in item &&
@@ -1653,6 +1688,82 @@ function normalizeContent(content) {
1653
1688
  }
1654
1689
  return normalized;
1655
1690
  }
1691
+ function normalizeDelegatedInput(input, projectRoot) {
1692
+ if (typeof input === "string") {
1693
+ if (!input.trim()) {
1694
+ throw new Error('run_agent requires a non-empty "input".');
1695
+ }
1696
+ return input;
1697
+ }
1698
+ if (!isRecord(input) && !Array.isArray(input)) {
1699
+ throw new Error("run_agent input must be text, one content item, or a content array.");
1700
+ }
1701
+ const content = normalizeContent(input, projectRoot);
1702
+ if (!content) {
1703
+ throw new Error("run_agent input must contain at least one valid content item.");
1704
+ }
1705
+ return content;
1706
+ }
1707
+ function agentContentInputSchema() {
1708
+ const textItem = {
1709
+ type: "object",
1710
+ properties: {
1711
+ type: { type: "string", enum: ["text"] },
1712
+ text: { type: "string", minLength: 1 },
1713
+ },
1714
+ required: ["type", "text"],
1715
+ additionalProperties: false,
1716
+ };
1717
+ const fileItems = [
1718
+ {
1719
+ type: "object",
1720
+ properties: {
1721
+ type: { type: "string", enum: ["file"] },
1722
+ filePath: { type: "string", minLength: 1 },
1723
+ mimeType: { type: "string", minLength: 1 },
1724
+ },
1725
+ required: ["filePath"],
1726
+ additionalProperties: false,
1727
+ },
1728
+ {
1729
+ type: "object",
1730
+ properties: {
1731
+ type: { type: "string", enum: ["url"] },
1732
+ url: { type: "string", minLength: 1 },
1733
+ mimeType: { type: "string", minLength: 1 },
1734
+ },
1735
+ required: ["url"],
1736
+ additionalProperties: false,
1737
+ },
1738
+ {
1739
+ type: "object",
1740
+ properties: {
1741
+ type: {
1742
+ type: "string",
1743
+ enum: ["image", "video", "audio", "url"],
1744
+ },
1745
+ fileUri: { type: "string", minLength: 1 },
1746
+ mimeType: { type: "string", minLength: 1 },
1747
+ },
1748
+ required: ["type", "fileUri", "mimeType"],
1749
+ additionalProperties: false,
1750
+ },
1751
+ ];
1752
+ const item = {
1753
+ anyOf: [textItem, ...fileItems],
1754
+ };
1755
+ return {
1756
+ anyOf: [
1757
+ { type: "string", minLength: 1 },
1758
+ item,
1759
+ {
1760
+ type: "array",
1761
+ items: item,
1762
+ minItems: 1,
1763
+ },
1764
+ ],
1765
+ };
1766
+ }
1656
1767
  function normalizeClientCapabilities(capabilities) {
1657
1768
  const names = [];
1658
1769
  for (const capability of capabilities) {
@@ -1750,7 +1861,8 @@ function buildSystemPrompt(manifest, events, variables) {
1750
1861
  if (subagents.length > 0) {
1751
1862
  sections.push([
1752
1863
  "## Available subagents",
1753
- "Use `run_agent` to start a specialist. Give it the complete task and only the context it needs.",
1864
+ "Use `run_agent` to start a specialist. Give it the complete task and only the context it needs. Its input accepts the same text and multimodal content format as a normal agent run.",
1865
+ "Local file paths in subagent input resolve from the configured Orcha project root.",
1754
1866
  "You may request multiple independent subagents in one response; Orcha runs them concurrently and returns each result by call ID.",
1755
1867
  "The call waits synchronously while the child is actively running, and the child's text response returns as a normal tool result.",
1756
1868
  "If a child pauses for a client action or because its session was paused, decide whether to resume it with `resume_agent`, inspect it with `inspect_agent`, invoke one of your own client actions for missing information, or continue without it.",
@@ -2281,6 +2393,10 @@ function isMessageContent(value) {
2281
2393
  if (value.type === "text") {
2282
2394
  return typeof value.text === "string";
2283
2395
  }
2396
+ if (value.type === "file") {
2397
+ return (typeof value.mimeType === "string" &&
2398
+ typeof value.filePath === "string");
2399
+ }
2284
2400
  return (["image", "video", "audio", "url"].includes(value.type) &&
2285
2401
  typeof value.mimeType === "string" &&
2286
2402
  typeof value.fileUri === "string");