@wiro-ai/wiro-mcp 1.3.3 → 1.4.0

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 (270) hide show
  1. package/README.md +173 -16
  2. package/dist/client.d.ts +10 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +39 -20
  5. package/dist/client.js.map +1 -1
  6. package/dist/index.js +4 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/instructions.d.ts +2 -0
  9. package/dist/instructions.d.ts.map +1 -0
  10. package/dist/instructions.js +25 -0
  11. package/dist/instructions.js.map +1 -0
  12. package/dist/playbooks.d.ts +8 -0
  13. package/dist/playbooks.d.ts.map +1 -0
  14. package/dist/playbooks.js +665 -0
  15. package/dist/playbooks.js.map +1 -0
  16. package/dist/prompts.d.ts +4 -0
  17. package/dist/prompts.d.ts.map +1 -0
  18. package/dist/prompts.js +133 -0
  19. package/dist/prompts.js.map +1 -0
  20. package/dist/server.d.ts +9 -2
  21. package/dist/server.d.ts.map +1 -1
  22. package/dist/server.js +9 -3
  23. package/dist/server.js.map +1 -1
  24. package/dist/tools/apply-agent-skills.d.ts +4 -0
  25. package/dist/tools/apply-agent-skills.d.ts.map +1 -0
  26. package/dist/tools/apply-agent-skills.js +545 -0
  27. package/dist/tools/apply-agent-skills.js.map +1 -0
  28. package/dist/tools/cancel-agent-message.d.ts +4 -0
  29. package/dist/tools/cancel-agent-message.d.ts.map +1 -0
  30. package/dist/tools/cancel-agent-message.js +126 -0
  31. package/dist/tools/cancel-agent-message.js.map +1 -0
  32. package/dist/tools/cancel-task.js +3 -3
  33. package/dist/tools/cancel-task.js.map +1 -1
  34. package/dist/tools/clone-workflow.d.ts +4 -0
  35. package/dist/tools/clone-workflow.d.ts.map +1 -0
  36. package/dist/tools/clone-workflow.js +83 -0
  37. package/dist/tools/clone-workflow.js.map +1 -0
  38. package/dist/tools/connect-agent-oauth.d.ts +4 -0
  39. package/dist/tools/connect-agent-oauth.d.ts.map +1 -0
  40. package/dist/tools/connect-agent-oauth.js +508 -0
  41. package/dist/tools/connect-agent-oauth.js.map +1 -0
  42. package/dist/tools/control-agent.d.ts +4 -0
  43. package/dist/tools/control-agent.d.ts.map +1 -0
  44. package/dist/tools/control-agent.js +274 -0
  45. package/dist/tools/control-agent.js.map +1 -0
  46. package/dist/tools/create-workflow.d.ts +100 -0
  47. package/dist/tools/create-workflow.d.ts.map +1 -0
  48. package/dist/tools/create-workflow.js +103 -0
  49. package/dist/tools/create-workflow.js.map +1 -0
  50. package/dist/tools/delete-agent-session.d.ts +6 -0
  51. package/dist/tools/delete-agent-session.d.ts.map +1 -0
  52. package/dist/tools/delete-agent-session.js +134 -0
  53. package/dist/tools/delete-agent-session.js.map +1 -0
  54. package/dist/tools/delete-agent.d.ts +4 -0
  55. package/dist/tools/delete-agent.d.ts.map +1 -0
  56. package/dist/tools/delete-agent.js +144 -0
  57. package/dist/tools/delete-agent.js.map +1 -0
  58. package/dist/tools/delete-workflow.d.ts +4 -0
  59. package/dist/tools/delete-workflow.d.ts.map +1 -0
  60. package/dist/tools/delete-workflow.js +77 -0
  61. package/dist/tools/delete-workflow.js.map +1 -0
  62. package/dist/tools/deploy-agent.d.ts +4 -0
  63. package/dist/tools/deploy-agent.d.ts.map +1 -0
  64. package/dist/tools/deploy-agent.js +455 -0
  65. package/dist/tools/deploy-agent.js.map +1 -0
  66. package/dist/tools/disconnect-agent-oauth.d.ts +4 -0
  67. package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -0
  68. package/dist/tools/disconnect-agent-oauth.js +118 -0
  69. package/dist/tools/disconnect-agent-oauth.js.map +1 -0
  70. package/dist/tools/get-account.d.ts +4 -0
  71. package/dist/tools/get-account.d.ts.map +1 -0
  72. package/dist/tools/get-account.js +110 -0
  73. package/dist/tools/get-account.js.map +1 -0
  74. package/dist/tools/get-agent-activity.d.ts +4 -0
  75. package/dist/tools/get-agent-activity.d.ts.map +1 -0
  76. package/dist/tools/get-agent-activity.js +383 -0
  77. package/dist/tools/get-agent-activity.js.map +1 -0
  78. package/dist/tools/get-agent-conversation.d.ts +4 -0
  79. package/dist/tools/get-agent-conversation.d.ts.map +1 -0
  80. package/dist/tools/get-agent-conversation.js +224 -0
  81. package/dist/tools/get-agent-conversation.js.map +1 -0
  82. package/dist/tools/get-agent-message.d.ts +4 -0
  83. package/dist/tools/get-agent-message.d.ts.map +1 -0
  84. package/dist/tools/get-agent-message.js +68 -0
  85. package/dist/tools/get-agent-message.js.map +1 -0
  86. package/dist/tools/get-agent-template.d.ts +4 -0
  87. package/dist/tools/get-agent-template.d.ts.map +1 -0
  88. package/dist/tools/get-agent-template.js +107 -0
  89. package/dist/tools/get-agent-template.js.map +1 -0
  90. package/dist/tools/get-model-schema.d.ts +5 -1
  91. package/dist/tools/get-model-schema.d.ts.map +1 -1
  92. package/dist/tools/get-model-schema.js +136 -8
  93. package/dist/tools/get-model-schema.js.map +1 -1
  94. package/dist/tools/get-my-agent.d.ts +4 -0
  95. package/dist/tools/get-my-agent.d.ts.map +1 -0
  96. package/dist/tools/get-my-agent.js +78 -0
  97. package/dist/tools/get-my-agent.js.map +1 -0
  98. package/dist/tools/get-playbook.d.ts +3 -0
  99. package/dist/tools/get-playbook.d.ts.map +1 -0
  100. package/dist/tools/get-playbook.js +50 -0
  101. package/dist/tools/get-playbook.js.map +1 -0
  102. package/dist/tools/get-task-price.d.ts.map +1 -1
  103. package/dist/tools/get-task-price.js +42 -15
  104. package/dist/tools/get-task-price.js.map +1 -1
  105. package/dist/tools/get-task.d.ts.map +1 -1
  106. package/dist/tools/get-task.js +4 -3
  107. package/dist/tools/get-task.js.map +1 -1
  108. package/dist/tools/get-workflow.d.ts +9 -0
  109. package/dist/tools/get-workflow.d.ts.map +1 -0
  110. package/dist/tools/get-workflow.js +99 -0
  111. package/dist/tools/get-workflow.js.map +1 -0
  112. package/dist/tools/index.d.ts +62 -2
  113. package/dist/tools/index.d.ts.map +1 -1
  114. package/dist/tools/index.js +127 -14
  115. package/dist/tools/index.js.map +1 -1
  116. package/dist/tools/kill-task.d.ts.map +1 -1
  117. package/dist/tools/kill-task.js +10 -6
  118. package/dist/tools/kill-task.js.map +1 -1
  119. package/dist/tools/list-agent-skills.d.ts +4 -0
  120. package/dist/tools/list-agent-skills.d.ts.map +1 -0
  121. package/dist/tools/list-agent-skills.js +242 -0
  122. package/dist/tools/list-agent-skills.js.map +1 -0
  123. package/dist/tools/list-agent-templates.d.ts +4 -0
  124. package/dist/tools/list-agent-templates.d.ts.map +1 -0
  125. package/dist/tools/list-agent-templates.js +110 -0
  126. package/dist/tools/list-agent-templates.js.map +1 -0
  127. package/dist/tools/list-my-agents.d.ts +4 -0
  128. package/dist/tools/list-my-agents.d.ts.map +1 -0
  129. package/dist/tools/list-my-agents.js +90 -0
  130. package/dist/tools/list-my-agents.js.map +1 -0
  131. package/dist/tools/list-studio-results.d.ts +4 -0
  132. package/dist/tools/list-studio-results.d.ts.map +1 -0
  133. package/dist/tools/list-studio-results.js +95 -0
  134. package/dist/tools/list-studio-results.js.map +1 -0
  135. package/dist/tools/list-tasks.d.ts.map +1 -1
  136. package/dist/tools/list-tasks.js +7 -5
  137. package/dist/tools/list-tasks.js.map +1 -1
  138. package/dist/tools/list-workflow-runs.d.ts +4 -0
  139. package/dist/tools/list-workflow-runs.d.ts.map +1 -0
  140. package/dist/tools/list-workflow-runs.js +110 -0
  141. package/dist/tools/list-workflow-runs.js.map +1 -0
  142. package/dist/tools/list-workflows.d.ts +4 -0
  143. package/dist/tools/list-workflows.d.ts.map +1 -0
  144. package/dist/tools/list-workflows.js +143 -0
  145. package/dist/tools/list-workflows.js.map +1 -0
  146. package/dist/tools/manage-agent-billing.d.ts +4 -0
  147. package/dist/tools/manage-agent-billing.d.ts.map +1 -0
  148. package/dist/tools/manage-agent-billing.js +764 -0
  149. package/dist/tools/manage-agent-billing.js.map +1 -0
  150. package/dist/tools/manage-agent-custom-skill.d.ts +4 -0
  151. package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -0
  152. package/dist/tools/manage-agent-custom-skill.js +537 -0
  153. package/dist/tools/manage-agent-custom-skill.js.map +1 -0
  154. package/dist/tools/recommend-model.d.ts.map +1 -1
  155. package/dist/tools/recommend-model.js +6 -5
  156. package/dist/tools/recommend-model.js.map +1 -1
  157. package/dist/tools/resume-workflow-run.d.ts +4 -0
  158. package/dist/tools/resume-workflow-run.d.ts.map +1 -0
  159. package/dist/tools/resume-workflow-run.js +69 -0
  160. package/dist/tools/resume-workflow-run.js.map +1 -0
  161. package/dist/tools/run-model.d.ts.map +1 -1
  162. package/dist/tools/run-model.js +39 -3
  163. package/dist/tools/run-model.js.map +1 -1
  164. package/dist/tools/run-workflow.d.ts +6 -0
  165. package/dist/tools/run-workflow.d.ts.map +1 -0
  166. package/dist/tools/run-workflow.js +132 -0
  167. package/dist/tools/run-workflow.js.map +1 -0
  168. package/dist/tools/save-workflow-from-agent.d.ts +4 -0
  169. package/dist/tools/save-workflow-from-agent.d.ts.map +1 -0
  170. package/dist/tools/save-workflow-from-agent.js +283 -0
  171. package/dist/tools/save-workflow-from-agent.js.map +1 -0
  172. package/dist/tools/schemas.d.ts +62 -12
  173. package/dist/tools/schemas.d.ts.map +1 -1
  174. package/dist/tools/schemas.js +17 -3
  175. package/dist/tools/schemas.js.map +1 -1
  176. package/dist/tools/search-models.js +6 -6
  177. package/dist/tools/search-models.js.map +1 -1
  178. package/dist/tools/send-agent-message.d.ts +4 -0
  179. package/dist/tools/send-agent-message.d.ts.map +1 -0
  180. package/dist/tools/send-agent-message.js +156 -0
  181. package/dist/tools/send-agent-message.js.map +1 -0
  182. package/dist/tools/set-agent-credentials.d.ts +4 -0
  183. package/dist/tools/set-agent-credentials.d.ts.map +1 -0
  184. package/dist/tools/set-agent-credentials.js +529 -0
  185. package/dist/tools/set-agent-credentials.js.map +1 -0
  186. package/dist/tools/update-agent.d.ts +4 -0
  187. package/dist/tools/update-agent.d.ts.map +1 -0
  188. package/dist/tools/update-agent.js +178 -0
  189. package/dist/tools/update-agent.js.map +1 -0
  190. package/dist/tools/update-workflow.d.ts +201 -0
  191. package/dist/tools/update-workflow.d.ts.map +1 -0
  192. package/dist/tools/update-workflow.js +168 -0
  193. package/dist/tools/update-workflow.js.map +1 -0
  194. package/dist/tools/upload-file.d.ts.map +1 -1
  195. package/dist/tools/upload-file.js +4 -3
  196. package/dist/tools/upload-file.js.map +1 -1
  197. package/dist/tools/wait-for-workflow-run.d.ts +259 -0
  198. package/dist/tools/wait-for-workflow-run.d.ts.map +1 -0
  199. package/dist/tools/wait-for-workflow-run.js +190 -0
  200. package/dist/tools/wait-for-workflow-run.js.map +1 -0
  201. package/dist/types.d.ts +22 -4
  202. package/dist/types.d.ts.map +1 -1
  203. package/dist/types.js.map +1 -1
  204. package/dist/utils/agent-catalog.d.ts +1256 -0
  205. package/dist/utils/agent-catalog.d.ts.map +1 -0
  206. package/dist/utils/agent-catalog.js +388 -0
  207. package/dist/utils/agent-catalog.js.map +1 -0
  208. package/dist/utils/agent-config.d.ts +441 -0
  209. package/dist/utils/agent-config.d.ts.map +1 -0
  210. package/dist/utils/agent-config.js +1667 -0
  211. package/dist/utils/agent-config.js.map +1 -0
  212. package/dist/utils/agent-message.d.ts +115 -0
  213. package/dist/utils/agent-message.d.ts.map +1 -0
  214. package/dist/utils/agent-message.js +246 -0
  215. package/dist/utils/agent-message.js.map +1 -0
  216. package/dist/utils/agent-targets.d.ts +76 -0
  217. package/dist/utils/agent-targets.d.ts.map +1 -0
  218. package/dist/utils/agent-targets.js +408 -0
  219. package/dist/utils/agent-targets.js.map +1 -0
  220. package/dist/utils/agent-turns.d.ts +354 -0
  221. package/dist/utils/agent-turns.d.ts.map +1 -0
  222. package/dist/utils/agent-turns.js +623 -0
  223. package/dist/utils/agent-turns.js.map +1 -0
  224. package/dist/utils/api.d.ts +40 -0
  225. package/dist/utils/api.d.ts.map +1 -0
  226. package/dist/utils/api.js +111 -0
  227. package/dist/utils/api.js.map +1 -0
  228. package/dist/utils/format.d.ts +42 -1
  229. package/dist/utils/format.d.ts.map +1 -1
  230. package/dist/utils/format.js +173 -14
  231. package/dist/utils/format.js.map +1 -1
  232. package/dist/utils/media.d.ts +28 -0
  233. package/dist/utils/media.d.ts.map +1 -0
  234. package/dist/utils/media.js +65 -0
  235. package/dist/utils/media.js.map +1 -0
  236. package/dist/utils/poll.d.ts +39 -0
  237. package/dist/utils/poll.d.ts.map +1 -0
  238. package/dist/utils/poll.js +111 -0
  239. package/dist/utils/poll.js.map +1 -0
  240. package/dist/utils/remote-file.js +2 -2
  241. package/dist/utils/remote-file.js.map +1 -1
  242. package/dist/utils/structured.d.ts +2 -0
  243. package/dist/utils/structured.d.ts.map +1 -1
  244. package/dist/utils/structured.js +10 -10
  245. package/dist/utils/structured.js.map +1 -1
  246. package/dist/utils/studio-runs.d.ts +87 -0
  247. package/dist/utils/studio-runs.d.ts.map +1 -0
  248. package/dist/utils/studio-runs.js +167 -0
  249. package/dist/utils/studio-runs.js.map +1 -0
  250. package/dist/utils/user-agent.d.ts +1140 -0
  251. package/dist/utils/user-agent.d.ts.map +1 -0
  252. package/dist/utils/user-agent.js +802 -0
  253. package/dist/utils/user-agent.js.map +1 -0
  254. package/dist/utils/workflow-agent.d.ts +151 -0
  255. package/dist/utils/workflow-agent.d.ts.map +1 -0
  256. package/dist/utils/workflow-agent.js +292 -0
  257. package/dist/utils/workflow-agent.js.map +1 -0
  258. package/dist/utils/workflow-artifacts.d.ts +160 -0
  259. package/dist/utils/workflow-artifacts.d.ts.map +1 -0
  260. package/dist/utils/workflow-artifacts.js +801 -0
  261. package/dist/utils/workflow-artifacts.js.map +1 -0
  262. package/dist/utils/workflow-save.d.ts +123 -0
  263. package/dist/utils/workflow-save.d.ts.map +1 -0
  264. package/dist/utils/workflow-save.js +734 -0
  265. package/dist/utils/workflow-save.js.map +1 -0
  266. package/dist/utils/workflow.d.ts +559 -0
  267. package/dist/utils/workflow.d.ts.map +1 -0
  268. package/dist/utils/workflow.js +613 -0
  269. package/dist/utils/workflow.js.map +1 -0
  270. package/package.json +1 -1
@@ -0,0 +1,801 @@
1
+ import { createHash } from 'node:crypto';
2
+ // The Workflow Agent answers in prose and puts workflows in fenced blocks:
3
+ // ```wiro-workflow holds a complete workflow definition and
4
+ // ```wiro-workflow-request asks the caller to list the user's workflows. These
5
+ // are the same rules wiro.ai applies before it offers to save an artifact, so a
6
+ // workflow that passes here passes there too. Saving and running still need
7
+ // the catalog checks the save tools do.
8
+ export const WORKFLOW_ARTIFACT_FENCE = 'wiro-workflow';
9
+ export const WORKFLOW_REQUEST_FENCE = 'wiro-workflow-request';
10
+ export const WORKFLOW_ARTIFACT_MAX_BYTES = 40 * 1024;
11
+ export const WORKFLOW_ASSISTANT_MAX_BYTES = 45 * 1024;
12
+ export const WORKFLOW_REQUEST_MAX_BYTES = 2 * 1024;
13
+ export const WORKFLOW_REQUEST_ID_MAX_LENGTH = 64;
14
+ export const WORKFLOW_LIST_SEARCH_MAX_LENGTH = 120;
15
+ export const WORKFLOW_LIST_LIMIT_MIN = 1;
16
+ export const WORKFLOW_LIST_LIMIT_MAX = 50;
17
+ const WORKFLOW_TITLE_MAX_LENGTH = 160;
18
+ const WORKFLOW_DESCRIPTION_MAX_LENGTH = 1_000;
19
+ const WORKFLOW_NODE_ID_PATTERN = /^node-[A-Za-z0-9_-]{1,64}$/;
20
+ export const WORKFLOW_TOOL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}\/[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
21
+ const WORKFLOW_UTILITY_PATTERN = /^workflow-utilities\/[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
22
+ const WORKFLOW_UTILITY_OUTPUT_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
23
+ const WORKFLOW_REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
24
+ const WORKFLOW_REFERENCE_TOKEN_PATTERN = /\$(?:input\.|node-)/;
25
+ const MAX_POSITION_MAGNITUDE = 1_000_000;
26
+ const WORKFLOW_JSON_MAX_DEPTH = 32;
27
+ function validationIssue(code, message, path = '$') {
28
+ return { code, message, path };
29
+ }
30
+ function isRecord(value) {
31
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
32
+ return false;
33
+ const prototype = Object.getPrototypeOf(value);
34
+ return prototype === Object.prototype || prototype === null;
35
+ }
36
+ function findReservedObjectKey(value, path = '$') {
37
+ const stack = [{ value, path }];
38
+ while (stack.length > 0) {
39
+ const current = stack.pop();
40
+ if (Array.isArray(current.value)) {
41
+ for (let index = current.value.length - 1; index >= 0; index--) {
42
+ stack.push({ value: current.value[index], path: `${current.path}[${index}]` });
43
+ }
44
+ continue;
45
+ }
46
+ if (!isRecord(current.value))
47
+ continue;
48
+ for (const [key, child] of Object.entries(current.value)) {
49
+ if (key === '__proto__' || key === 'prototype' || key === 'constructor') {
50
+ return `${current.path}.${key}`;
51
+ }
52
+ stack.push({ value: child, path: `${current.path}.${key}` });
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ function workflowJsonDepth(value) {
58
+ const stack = [{ value, depth: 1 }];
59
+ let maximum = 1;
60
+ while (stack.length > 0) {
61
+ const current = stack.pop();
62
+ maximum = Math.max(maximum, current.depth);
63
+ if (maximum > WORKFLOW_JSON_MAX_DEPTH)
64
+ return maximum;
65
+ if (Array.isArray(current.value)) {
66
+ for (const child of current.value)
67
+ stack.push({ value: child, depth: current.depth + 1 });
68
+ }
69
+ else if (isRecord(current.value)) {
70
+ for (const child of Object.values(current.value))
71
+ stack.push({ value: child, depth: current.depth + 1 });
72
+ }
73
+ }
74
+ return maximum;
75
+ }
76
+ function hasOnlyKeys(value, allowedKeys) {
77
+ const allowed = new Set(allowedKeys);
78
+ return Object.keys(value).every(key => allowed.has(key));
79
+ }
80
+ function isSafeFiniteNumber(value) {
81
+ return typeof value === 'number' && Number.isFinite(value);
82
+ }
83
+ function isSafePosition(value) {
84
+ if (!isRecord(value) || !isSafeFiniteNumber(value.x) || !isSafeFiniteNumber(value.y))
85
+ return false;
86
+ return Math.abs(value.x) <= MAX_POSITION_MAGNITUDE && Math.abs(value.y) <= MAX_POSITION_MAGNITUDE;
87
+ }
88
+ function isStringRecord(value) {
89
+ return isRecord(value) && Object.values(value).every(item => typeof item === 'string');
90
+ }
91
+ function canonicalSerialize(value, seen) {
92
+ if (value === null)
93
+ return 'null';
94
+ switch (typeof value) {
95
+ case 'string':
96
+ return JSON.stringify(value);
97
+ case 'boolean':
98
+ return value ? 'true' : 'false';
99
+ case 'number':
100
+ return Number.isFinite(value) ? JSON.stringify(value) : 'null';
101
+ case 'undefined':
102
+ case 'function':
103
+ case 'symbol':
104
+ return 'null';
105
+ case 'bigint':
106
+ throw new TypeError('BigInt values cannot be canonicalized as JSON');
107
+ default:
108
+ break;
109
+ }
110
+ const objectValue = value;
111
+ if (seen.has(objectValue)) {
112
+ throw new TypeError('Circular values cannot be canonicalized');
113
+ }
114
+ seen.add(objectValue);
115
+ try {
116
+ if (Array.isArray(value)) {
117
+ return `[${value.map(item => canonicalSerialize(item, seen)).join(',')}]`;
118
+ }
119
+ const record = value;
120
+ const entries = Object.keys(record)
121
+ .filter(key => {
122
+ const itemType = typeof record[key];
123
+ return itemType !== 'undefined' && itemType !== 'function' && itemType !== 'symbol';
124
+ })
125
+ .sort()
126
+ .map(key => `${JSON.stringify(key)}:${canonicalSerialize(record[key], seen)}`);
127
+ return `{${entries.join(',')}}`;
128
+ }
129
+ finally {
130
+ seen.delete(objectValue);
131
+ }
132
+ }
133
+ /** Deterministic JSON: object keys are sorted recursively, array order is kept. */
134
+ export function canonicalStringify(value) {
135
+ return canonicalSerialize(value, new WeakSet());
136
+ }
137
+ /** Lowercase hex SHA-256 of the canonical JSON, the same value wiro.ai computes. */
138
+ export function canonicalHash(value) {
139
+ return createHash('sha256').update(canonicalStringify(value), 'utf8').digest('hex');
140
+ }
141
+ export function utf8ByteLength(value) {
142
+ return Buffer.byteLength(value, 'utf8');
143
+ }
144
+ export function createWorkflowArtifactIdentity(messageGuid, artifact) {
145
+ return `${encodeURIComponent(messageGuid)}:${canonicalHash(artifact)}`;
146
+ }
147
+ function validateWorkflowSchema(schemaValue, issues) {
148
+ if (!isRecord(schemaValue)) {
149
+ issues.push(validationIssue('invalid-schema', 'Workflow schema must be an object', '$.contents.schema'));
150
+ return;
151
+ }
152
+ if (!hasOnlyKeys(schemaValue, ['input', 'output'])) {
153
+ issues.push(validationIssue('unsupported-schema-field', 'Workflow schema contains unsupported fields', '$.contents.schema'));
154
+ }
155
+ if (!isRecord(schemaValue.input)) {
156
+ issues.push(validationIssue('invalid-schema-input', 'Workflow schema.input must be an object', '$.contents.schema.input'));
157
+ }
158
+ else {
159
+ for (const [key, field] of Object.entries(schemaValue.input)) {
160
+ if (!key || !isRecord(field)) {
161
+ issues.push(validationIssue('invalid-schema-field', `Workflow input "${key}" must be an object`, `$.contents.schema.input.${key}`));
162
+ continue;
163
+ }
164
+ if (!hasOnlyKeys(field, ['name', 'label', 'type', 'description', 'required', 'defaultValue', 'examples', 'ui', 'modelId', 'sourceParamType'])) {
165
+ issues.push(validationIssue('unsupported-schema-input-field', `Workflow input "${key}" contains unsupported fields`, `$.contents.schema.input.${key}`));
166
+ }
167
+ if (typeof field.name !== 'string' || !field.name.trim() || typeof field.label !== 'string' || !field.label.trim()) {
168
+ issues.push(validationIssue('invalid-schema-field-name', `Workflow input "${key}" must declare name and label strings`, `$.contents.schema.input.${key}`));
169
+ }
170
+ if (typeof field.description !== 'string') {
171
+ issues.push(validationIssue('invalid-schema-field-description', `Workflow input "${key}" description must be a string`, `$.contents.schema.input.${key}.description`));
172
+ }
173
+ const validType = typeof field.type === 'string' || (isRecord(field.type) && typeof field.type.kind === 'string' && typeof field.type.elementType === 'string');
174
+ if (!validType) {
175
+ issues.push(validationIssue('invalid-schema-field-type', `Workflow input "${key}" has an invalid type`, `$.contents.schema.input.${key}.type`));
176
+ }
177
+ if (!isRecord(field.ui)) {
178
+ issues.push(validationIssue('invalid-schema-field-ui', `Workflow input "${key}" has an invalid ui object`, `$.contents.schema.input.${key}.ui`));
179
+ }
180
+ if (!Array.isArray(field.examples)) {
181
+ issues.push(validationIssue('invalid-schema-field-examples', `Workflow input "${key}" examples must be an array`, `$.contents.schema.input.${key}.examples`));
182
+ }
183
+ if (typeof field.required !== 'boolean') {
184
+ issues.push(validationIssue('invalid-schema-field-required', `Workflow input "${key}" required must be boolean`, `$.contents.schema.input.${key}.required`));
185
+ }
186
+ if (!Object.prototype.hasOwnProperty.call(field, 'defaultValue')) {
187
+ issues.push(validationIssue('missing-schema-field-default', `Workflow input "${key}" must declare defaultValue`, `$.contents.schema.input.${key}.defaultValue`));
188
+ }
189
+ if (field.modelId !== undefined && field.modelId !== null && typeof field.modelId !== 'string' && typeof field.modelId !== 'number') {
190
+ issues.push(validationIssue('invalid-schema-field-model', `Workflow input "${key}" modelId must be a string, number, or null`, `$.contents.schema.input.${key}.modelId`));
191
+ }
192
+ if (field.sourceParamType !== undefined && field.sourceParamType !== null && typeof field.sourceParamType !== 'string') {
193
+ issues.push(validationIssue('invalid-schema-field-source-type', `Workflow input "${key}" sourceParamType must be a string or null`, `$.contents.schema.input.${key}.sourceParamType`));
194
+ }
195
+ }
196
+ }
197
+ if (!isRecord(schemaValue.output)) {
198
+ issues.push(validationIssue('invalid-schema-output', 'Workflow schema.output must be an object', '$.contents.schema.output'));
199
+ }
200
+ else if (Object.keys(schemaValue.output).length > 0) {
201
+ issues.push(validationIssue('noncanonical-schema-output', 'Workflow schema.output must remain empty; End fields and top-level output own output mappings', '$.contents.schema.output'));
202
+ }
203
+ }
204
+ function validateWorkflowMetadata(metadataValue, issues) {
205
+ if (!isRecord(metadataValue) || !isRecord(metadataValue.input)) {
206
+ issues.push(validationIssue('invalid-workflow-metadata', 'Workflow metadata.input must be an object', '$.contents.metadata.input'));
207
+ return;
208
+ }
209
+ if (!hasOnlyKeys(metadataValue, ['input']) || !hasOnlyKeys(metadataValue.input, ['position'])) {
210
+ issues.push(validationIssue('unsupported-workflow-metadata', 'Workflow artifacts may contain only metadata.input.position', '$.contents.metadata'));
211
+ }
212
+ if (!isSafePosition(metadataValue.input.position)) {
213
+ issues.push(validationIssue('invalid-input-position', 'Workflow Start position must contain finite x and y values', '$.contents.metadata.input.position'));
214
+ }
215
+ // Saved Start values belong to the user. An artifact may never set them.
216
+ if (metadataValue.input.values !== undefined) {
217
+ issues.push(validationIssue('protected-input-values-forbidden', 'Workflow artifacts cannot set saved Start input values', '$.contents.metadata.input.values'));
218
+ }
219
+ }
220
+ function validateRunNodeMetadata(metadataValue, path, issues) {
221
+ if (!isRecord(metadataValue)) {
222
+ issues.push(validationIssue('invalid-node-metadata', 'Run node metadata must be an object', path));
223
+ return;
224
+ }
225
+ if (!hasOnlyKeys(metadataValue, ['position', 'locked', 'multifileActiveParam'])) {
226
+ issues.push(validationIssue('unsupported-node-metadata', 'Run node metadata contains unsupported fields', path));
227
+ }
228
+ if (!isSafePosition(metadataValue.position)) {
229
+ issues.push(validationIssue('invalid-node-position', 'Run node position must contain finite x and y values', `${path}.position`));
230
+ }
231
+ if (metadataValue.locked !== undefined && typeof metadataValue.locked !== 'boolean') {
232
+ issues.push(validationIssue('invalid-node-locked', 'Run node locked metadata must be boolean', `${path}.locked`));
233
+ }
234
+ if (metadataValue.multifileActiveParam !== undefined && !isStringRecord(metadataValue.multifileActiveParam)) {
235
+ issues.push(validationIssue('invalid-node-multifile-metadata', 'Run node multifileActiveParam must map strings to strings', `${path}.multifileActiveParam`));
236
+ }
237
+ }
238
+ function validateDisplayNode(nodeValue, issues) {
239
+ const path = '$.contents.nodes.output';
240
+ if (!isRecord(nodeValue)) {
241
+ issues.push(validationIssue('missing-output-node', 'Workflow nodes.output must be the canonical display node', path));
242
+ return;
243
+ }
244
+ if (!hasOnlyKeys(nodeValue, ['type', 'id', 'depends', 'input', 'fields', 'metadata'])) {
245
+ issues.push(validationIssue('unsupported-output-node-field', 'Workflow output node contains unsupported fields', path));
246
+ }
247
+ if (nodeValue.type !== 'display') {
248
+ issues.push(validationIssue('invalid-output-node-type', 'Workflow nodes.output type must be "display"', `${path}.type`));
249
+ }
250
+ if (nodeValue.id !== 'output') {
251
+ issues.push(validationIssue('invalid-output-node-id', 'Workflow nodes.output id must be "output"', `${path}.id`));
252
+ }
253
+ if (!Array.isArray(nodeValue.depends) || nodeValue.depends.some(item => typeof item !== 'string')) {
254
+ issues.push(validationIssue('invalid-output-depends', 'Workflow output depends must be an array of node ids', `${path}.depends`));
255
+ }
256
+ if (!isRecord(nodeValue.input) || Object.keys(nodeValue.input).length > 0) {
257
+ issues.push(validationIssue('invalid-output-input', 'Workflow output input must be an empty object', `${path}.input`));
258
+ }
259
+ if (!isStringRecord(nodeValue.fields)) {
260
+ issues.push(validationIssue('invalid-output-fields', 'Workflow output fields must map names to references', `${path}.fields`));
261
+ }
262
+ if (!isRecord(nodeValue.metadata) || !isSafePosition(nodeValue.metadata.position)) {
263
+ issues.push(validationIssue('invalid-output-position', 'Workflow End position must contain finite x and y values', `${path}.metadata.position`));
264
+ }
265
+ else if (!hasOnlyKeys(nodeValue.metadata, ['position'])) {
266
+ issues.push(validationIssue('unsupported-output-metadata', 'Workflow End metadata may contain only position', `${path}.metadata`));
267
+ }
268
+ }
269
+ function walkInputStrings(value, path, callback, seen = new WeakSet()) {
270
+ if (typeof value === 'string') {
271
+ callback(value, path);
272
+ return;
273
+ }
274
+ if (value === null || typeof value !== 'object')
275
+ return;
276
+ if (seen.has(value))
277
+ return;
278
+ seen.add(value);
279
+ if (Array.isArray(value)) {
280
+ value.forEach((item, index) => walkInputStrings(item, `${path}[${index}]`, callback, seen));
281
+ return;
282
+ }
283
+ for (const [key, item] of Object.entries(value)) {
284
+ walkInputStrings(item, `${path}.${key}`, callback, seen);
285
+ }
286
+ }
287
+ // Lookups use own properties only, so "toString" or "constructor" can never
288
+ // pass for a declared input or node.
289
+ function validateReference(reference, path, consumerId, consumerDepends, nodes, schemaInput, issues) {
290
+ if (reference.startsWith('$input.')) {
291
+ const inputKey = reference.slice('$input.'.length);
292
+ if (!inputKey || !Object.hasOwn(schemaInput, inputKey)) {
293
+ issues.push(validationIssue('unknown-input-reference', `Reference targets unknown workflow input "${inputKey}"`, path));
294
+ }
295
+ if (!consumerDepends.has('input')) {
296
+ issues.push(validationIssue('reference-missing-dependency', `Reference "${reference}" is not represented in depends`, path));
297
+ }
298
+ return;
299
+ }
300
+ if (!reference.startsWith('$node-')) {
301
+ issues.push(validationIssue('malformed-reference', `Unsupported workflow reference "${reference}"`, path));
302
+ return;
303
+ }
304
+ const separatorIndex = reference.indexOf('.', 1);
305
+ if (separatorIndex < 0 || separatorIndex === reference.length - 1) {
306
+ issues.push(validationIssue('malformed-reference', `Malformed workflow node reference "${reference}"`, path));
307
+ return;
308
+ }
309
+ const sourceId = reference.slice(1, separatorIndex);
310
+ const outputKey = reference.slice(separatorIndex + 1);
311
+ const source = nodes[sourceId];
312
+ if (!source || sourceId === 'output' || !isRecord(source) || source.type !== 'run') {
313
+ issues.push(validationIssue('unknown-node-reference', `Reference targets unknown run node "${sourceId}"`, path));
314
+ return;
315
+ }
316
+ if (sourceId === consumerId) {
317
+ issues.push(validationIssue('self-reference', `Node "${consumerId}" cannot reference itself`, path));
318
+ }
319
+ if (!consumerDepends.has(sourceId)) {
320
+ issues.push(validationIssue('reference-missing-dependency', `Reference "${reference}" is not represented in depends`, path));
321
+ }
322
+ const sourceTool = typeof source.tool === 'string' ? source.tool : '';
323
+ if (sourceTool.startsWith('workflow-utilities/')) {
324
+ if (!WORKFLOW_UTILITY_OUTPUT_PATTERN.test(outputKey)) {
325
+ issues.push(validationIssue('invalid-utility-output-reference', `Utility output "${outputKey}" is not a valid port key`, path));
326
+ }
327
+ }
328
+ else if (outputKey !== 'outputs') {
329
+ issues.push(validationIssue('invalid-tool-output-reference', `Model node "${sourceId}" must be referenced through ".outputs"`, path));
330
+ }
331
+ }
332
+ function validateDependencyGraph(nodes, issues) {
333
+ const graphIds = Object.keys(nodes);
334
+ const inDegree = new Map();
335
+ const dependents = new Map();
336
+ graphIds.forEach(id => {
337
+ inDegree.set(id, 0);
338
+ dependents.set(id, []);
339
+ });
340
+ for (const [id, nodeValue] of Object.entries(nodes)) {
341
+ if (!isRecord(nodeValue) || !Array.isArray(nodeValue.depends))
342
+ continue;
343
+ for (const dependency of nodeValue.depends) {
344
+ if (dependency === 'input' || typeof dependency !== 'string' || !inDegree.has(dependency))
345
+ continue;
346
+ inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
347
+ dependents.get(dependency)?.push(id);
348
+ }
349
+ }
350
+ const queue = graphIds.filter(id => inDegree.get(id) === 0);
351
+ let visited = 0;
352
+ while (queue.length > 0) {
353
+ const current = queue.shift();
354
+ visited += 1;
355
+ for (const dependent of dependents.get(current) ?? []) {
356
+ const nextDegree = (inDegree.get(dependent) ?? 0) - 1;
357
+ inDegree.set(dependent, nextDegree);
358
+ if (nextDegree === 0)
359
+ queue.push(dependent);
360
+ }
361
+ }
362
+ if (visited !== graphIds.length) {
363
+ issues.push(validationIssue('workflow-cycle', 'Workflow dependencies contain a cycle', '$.contents.nodes'));
364
+ }
365
+ }
366
+ export function validateWorkflowContents(value) {
367
+ const issues = [];
368
+ if (!isRecord(value)) {
369
+ return {
370
+ valid: false,
371
+ status: 'invalid',
372
+ issues: [validationIssue('invalid-contents', 'Workflow contents must be an object', '$.contents')],
373
+ };
374
+ }
375
+ const reservedPath = findReservedObjectKey(value, '$.contents');
376
+ if (reservedPath) {
377
+ return {
378
+ valid: false,
379
+ status: 'invalid',
380
+ issues: [validationIssue('reserved-object-key', 'Workflow artifacts cannot contain reserved object keys', reservedPath)],
381
+ };
382
+ }
383
+ if (workflowJsonDepth(value) > WORKFLOW_JSON_MAX_DEPTH) {
384
+ return {
385
+ valid: false,
386
+ status: 'invalid',
387
+ issues: [validationIssue('workflow-json-too-deep', `Workflow artifact nesting cannot exceed ${WORKFLOW_JSON_MAX_DEPTH} levels`, '$.contents')],
388
+ };
389
+ }
390
+ if (!hasOnlyKeys(value, ['version', 'nodes', 'output', 'schema', 'metadata'])) {
391
+ issues.push(validationIssue('unsupported-contents-field', 'Workflow contents contains unsupported fields', '$.contents'));
392
+ }
393
+ if (value.version !== '1') {
394
+ issues.push(validationIssue('unsupported-workflow-version', 'Workflow contents.version must be "1"', '$.contents.version'));
395
+ }
396
+ if (!isRecord(value.nodes)) {
397
+ issues.push(validationIssue('invalid-nodes', 'Workflow nodes must be an object, not an array', '$.contents.nodes'));
398
+ }
399
+ if (!isStringRecord(value.output)) {
400
+ issues.push(validationIssue('invalid-top-level-output', 'Workflow output must map names to references', '$.contents.output'));
401
+ }
402
+ validateWorkflowSchema(value.schema, issues);
403
+ validateWorkflowMetadata(value.metadata, issues);
404
+ if (!isRecord(value.nodes)) {
405
+ return { valid: false, status: 'invalid', issues };
406
+ }
407
+ const nodes = value.nodes;
408
+ validateDisplayNode(nodes.output, issues);
409
+ const seenNodeIds = new Set();
410
+ for (const [nodeKey, nodeValue] of Object.entries(nodes)) {
411
+ if (nodeKey === 'output')
412
+ continue;
413
+ const path = `$.contents.nodes.${nodeKey}`;
414
+ if (!WORKFLOW_NODE_ID_PATTERN.test(nodeKey)) {
415
+ issues.push(validationIssue('invalid-node-key', `Run node key "${nodeKey}" must start with "node-" and contain only safe id characters`, path));
416
+ }
417
+ if (!isRecord(nodeValue)) {
418
+ issues.push(validationIssue('invalid-run-node', `Run node "${nodeKey}" must be an object`, path));
419
+ continue;
420
+ }
421
+ if (!hasOnlyKeys(nodeValue, ['type', 'id', 'tool', 'depends', 'input', 'metadata'])) {
422
+ issues.push(validationIssue('unsupported-run-node-field', `Node "${nodeKey}" contains unsupported fields`, path));
423
+ }
424
+ if (nodeValue.type !== 'run') {
425
+ issues.push(validationIssue('invalid-run-node-type', `Node "${nodeKey}" type must be "run"`, `${path}.type`));
426
+ }
427
+ if (nodeValue.id !== nodeKey) {
428
+ issues.push(validationIssue('node-id-mismatch', `Node "${nodeKey}" id must match its object key`, `${path}.id`));
429
+ }
430
+ if (typeof nodeValue.id === 'string') {
431
+ if (seenNodeIds.has(nodeValue.id)) {
432
+ issues.push(validationIssue('duplicate-node-id', `Node id "${nodeValue.id}" is duplicated`, `${path}.id`));
433
+ }
434
+ seenNodeIds.add(nodeValue.id);
435
+ }
436
+ if (typeof nodeValue.tool !== 'string' || (!WORKFLOW_TOOL_PATTERN.test(nodeValue.tool) && !WORKFLOW_UTILITY_PATTERN.test(nodeValue.tool))) {
437
+ issues.push(validationIssue('invalid-node-tool', `Node "${nodeKey}" must use an owner/model or workflow-utilities/* slug`, `${path}.tool`));
438
+ }
439
+ if (!Array.isArray(nodeValue.depends) || nodeValue.depends.some(dependency => typeof dependency !== 'string')) {
440
+ issues.push(validationIssue('invalid-node-depends', `Node "${nodeKey}" depends must be an array of node ids`, `${path}.depends`));
441
+ }
442
+ else {
443
+ const dependencySet = new Set();
444
+ for (const dependency of nodeValue.depends) {
445
+ if (dependencySet.has(dependency)) {
446
+ issues.push(validationIssue('duplicate-node-dependency', `Node "${nodeKey}" contains duplicate dependency "${dependency}"`, `${path}.depends`));
447
+ }
448
+ dependencySet.add(dependency);
449
+ if (dependency === nodeKey) {
450
+ issues.push(validationIssue('self-dependency', `Node "${nodeKey}" cannot depend on itself`, `${path}.depends`));
451
+ }
452
+ else if (dependency !== 'input' && (dependency === 'output' || !Object.hasOwn(nodes, dependency))) {
453
+ issues.push(validationIssue('unknown-node-dependency', `Node "${nodeKey}" depends on unknown run node "${dependency}"`, `${path}.depends`));
454
+ }
455
+ }
456
+ }
457
+ if (!isRecord(nodeValue.input)) {
458
+ issues.push(validationIssue('invalid-node-input', `Node "${nodeKey}" input must be an object`, `${path}.input`));
459
+ }
460
+ validateRunNodeMetadata(nodeValue.metadata, `${path}.metadata`, issues);
461
+ }
462
+ const outputNode = isRecord(nodes.output) ? nodes.output : null;
463
+ if (outputNode && Array.isArray(outputNode.depends)) {
464
+ const dependencySet = new Set();
465
+ for (const dependency of outputNode.depends) {
466
+ if (typeof dependency !== 'string')
467
+ continue;
468
+ if (dependencySet.has(dependency)) {
469
+ issues.push(validationIssue('duplicate-output-dependency', `Workflow output contains duplicate dependency "${dependency}"`, '$.contents.nodes.output.depends'));
470
+ }
471
+ dependencySet.add(dependency);
472
+ if (dependency !== 'input' && (dependency === 'output' || !Object.hasOwn(nodes, dependency))) {
473
+ issues.push(validationIssue('unknown-output-dependency', `Workflow output depends on unknown run node "${dependency}"`, '$.contents.nodes.output.depends'));
474
+ }
475
+ }
476
+ }
477
+ const schemaInput = isRecord(value.schema) && isRecord(value.schema.input) ? value.schema.input : {};
478
+ for (const [nodeKey, nodeValue] of Object.entries(nodes)) {
479
+ if (nodeKey === 'output' || !isRecord(nodeValue) || !isRecord(nodeValue.input) || !Array.isArray(nodeValue.depends))
480
+ continue;
481
+ const depends = new Set(nodeValue.depends.filter((item) => typeof item === 'string'));
482
+ walkInputStrings(nodeValue.input, `$.contents.nodes.${nodeKey}.input`, (item, path) => {
483
+ if (item.startsWith('$')) {
484
+ validateReference(item, path, nodeKey, depends, nodes, schemaInput, issues);
485
+ }
486
+ else if (WORKFLOW_REFERENCE_TOKEN_PATTERN.test(item)) {
487
+ issues.push(validationIssue('embedded-workflow-reference', 'Workflow references must occupy the entire JSON string value; use an exact reference or a workflow utility to compose text', path));
488
+ }
489
+ });
490
+ }
491
+ if (outputNode && isStringRecord(outputNode.fields) && Array.isArray(outputNode.depends)) {
492
+ const depends = new Set(outputNode.depends.filter((item) => typeof item === 'string'));
493
+ for (const [key, reference] of Object.entries(outputNode.fields)) {
494
+ if (!reference.startsWith('$')) {
495
+ issues.push(validationIssue('invalid-output-reference', `Workflow output "${key}" must be a workflow reference`, `$.contents.nodes.output.fields.${key}`));
496
+ continue;
497
+ }
498
+ validateReference(reference, `$.contents.nodes.output.fields.${key}`, 'output', depends, nodes, schemaInput, issues);
499
+ }
500
+ }
501
+ if (isStringRecord(value.output) && outputNode && isStringRecord(outputNode.fields) && canonicalStringify(value.output) !== canonicalStringify(outputNode.fields)) {
502
+ issues.push(validationIssue('output-mapping-mismatch', 'Workflow output must exactly match nodes.output.fields', '$.contents.output'));
503
+ }
504
+ validateDependencyGraph(nodes, issues);
505
+ if (issues.length > 0) {
506
+ return { valid: false, status: 'invalid', issues };
507
+ }
508
+ return {
509
+ valid: true,
510
+ status: 'valid',
511
+ value: value,
512
+ issues: [],
513
+ };
514
+ }
515
+ export function validateWorkflowArtifact(value) {
516
+ const issues = [];
517
+ if (!isRecord(value)) {
518
+ return {
519
+ valid: false,
520
+ status: 'invalid',
521
+ issues: [validationIssue('invalid-artifact', 'Workflow artifact must be an object')],
522
+ };
523
+ }
524
+ if (value.artifactVersion !== 1) {
525
+ issues.push(validationIssue('unsupported-artifact-version', 'Workflow artifactVersion must be 1', '$.artifactVersion'));
526
+ }
527
+ const title = typeof value.title === 'string' ? value.title.trim() : '';
528
+ if (!title || title.length > WORKFLOW_TITLE_MAX_LENGTH) {
529
+ issues.push(validationIssue('invalid-artifact-title', `Workflow title must be 1-${WORKFLOW_TITLE_MAX_LENGTH} characters`, '$.title'));
530
+ }
531
+ const description = value.description === undefined ? '' : typeof value.description === 'string' ? value.description.trim() : '';
532
+ if (value.description !== undefined && typeof value.description !== 'string') {
533
+ issues.push(validationIssue('invalid-artifact-description', 'Workflow description must be a string', '$.description'));
534
+ }
535
+ else if (description.length > WORKFLOW_DESCRIPTION_MAX_LENGTH) {
536
+ issues.push(validationIssue('artifact-description-too-long', `Workflow description must be at most ${WORKFLOW_DESCRIPTION_MAX_LENGTH} characters`, '$.description'));
537
+ }
538
+ const contentsResult = validateWorkflowContents(value.contents);
539
+ issues.push(...contentsResult.issues);
540
+ if (issues.length > 0 || !contentsResult.value) {
541
+ return { valid: false, status: 'invalid', issues };
542
+ }
543
+ // Copy only the fields the artifact contract defines. Ids or provenance the
544
+ // model adds are never kept or hashed.
545
+ const artifact = {
546
+ artifactVersion: 1,
547
+ title,
548
+ description,
549
+ contents: contentsResult.value,
550
+ };
551
+ return { valid: true, status: 'valid', value: artifact, issues: [] };
552
+ }
553
+ export function validateWorkflowArtifactJson(raw) {
554
+ const byteLength = utf8ByteLength(raw);
555
+ if (byteLength > WORKFLOW_ARTIFACT_MAX_BYTES) {
556
+ return {
557
+ valid: false,
558
+ status: 'oversize',
559
+ byteLength,
560
+ issues: [validationIssue('workflow-artifact-oversize', `Workflow artifact exceeds the ${WORKFLOW_ARTIFACT_MAX_BYTES}-byte UTF-8 limit`)],
561
+ };
562
+ }
563
+ let parsed;
564
+ try {
565
+ parsed = JSON.parse(raw);
566
+ }
567
+ catch {
568
+ return {
569
+ valid: false,
570
+ status: 'invalid',
571
+ byteLength,
572
+ issues: [validationIssue('malformed-workflow-json', 'Workflow artifact contains malformed JSON')],
573
+ };
574
+ }
575
+ return { ...validateWorkflowArtifact(parsed), byteLength };
576
+ }
577
+ export function validateWorkflowListRequest(value) {
578
+ const issues = [];
579
+ if (!isRecord(value)) {
580
+ return {
581
+ valid: false,
582
+ status: 'invalid',
583
+ issues: [validationIssue('invalid-workflow-request', 'Workflow request must be an object')],
584
+ };
585
+ }
586
+ if (!hasOnlyKeys(value, ['protocolVersion', 'requestId', 'operation', 'args'])) {
587
+ issues.push(validationIssue('unsupported-workflow-request-field', 'Workflow request contains unsupported fields'));
588
+ }
589
+ if (value.protocolVersion !== 1) {
590
+ issues.push(validationIssue('unsupported-request-version', 'Workflow request protocolVersion must be 1', '$.protocolVersion'));
591
+ }
592
+ if (value.operation !== 'workflow.list') {
593
+ issues.push(validationIssue('unsupported-workflow-operation', 'Only the read-only workflow.list operation is allowed', '$.operation'));
594
+ }
595
+ const requestId = typeof value.requestId === 'string' ? value.requestId : '';
596
+ if (!requestId || requestId.length > WORKFLOW_REQUEST_ID_MAX_LENGTH || !WORKFLOW_REQUEST_ID_PATTERN.test(requestId)) {
597
+ issues.push(validationIssue('invalid-workflow-request-id', `requestId must be a safe 1-${WORKFLOW_REQUEST_ID_MAX_LENGTH} character token`, '$.requestId'));
598
+ }
599
+ if (!isRecord(value.args)) {
600
+ issues.push(validationIssue('invalid-workflow-request-args', 'workflow.list args must be an object', '$.args'));
601
+ }
602
+ else if (!hasOnlyKeys(value.args, ['search', 'limit'])) {
603
+ issues.push(validationIssue('unsupported-workflow-request-arg', 'workflow.list accepts only search and limit arguments', '$.args'));
604
+ }
605
+ const argsValue = isRecord(value.args) ? value.args : {};
606
+ let search;
607
+ if (argsValue.search !== undefined) {
608
+ if (typeof argsValue.search !== 'string') {
609
+ issues.push(validationIssue('invalid-workflow-search', 'workflow.list search must be a string', '$.args.search'));
610
+ }
611
+ else {
612
+ search = argsValue.search.trim();
613
+ if (!search || search.length > WORKFLOW_LIST_SEARCH_MAX_LENGTH || /[\u0000-\u001F\u007F]/.test(search)) {
614
+ issues.push(validationIssue('invalid-workflow-search', `workflow.list search must be 1-${WORKFLOW_LIST_SEARCH_MAX_LENGTH} visible characters`, '$.args.search'));
615
+ }
616
+ }
617
+ }
618
+ let limit;
619
+ if (argsValue.limit !== undefined) {
620
+ if (!Number.isInteger(argsValue.limit) || argsValue.limit < WORKFLOW_LIST_LIMIT_MIN || argsValue.limit > WORKFLOW_LIST_LIMIT_MAX) {
621
+ issues.push(validationIssue('invalid-workflow-limit', `workflow.list limit must be an integer from ${WORKFLOW_LIST_LIMIT_MIN} to ${WORKFLOW_LIST_LIMIT_MAX}`, '$.args.limit'));
622
+ }
623
+ else {
624
+ limit = argsValue.limit;
625
+ }
626
+ }
627
+ if (issues.length > 0) {
628
+ return { valid: false, status: 'invalid', issues };
629
+ }
630
+ const args = {};
631
+ if (search !== undefined)
632
+ args.search = search;
633
+ if (limit !== undefined)
634
+ args.limit = limit;
635
+ return {
636
+ valid: true,
637
+ status: 'valid',
638
+ value: {
639
+ protocolVersion: 1,
640
+ requestId,
641
+ operation: 'workflow.list',
642
+ args,
643
+ },
644
+ issues: [],
645
+ };
646
+ }
647
+ export function validateWorkflowListRequestJson(raw) {
648
+ const byteLength = utf8ByteLength(raw);
649
+ if (byteLength > WORKFLOW_REQUEST_MAX_BYTES) {
650
+ return {
651
+ valid: false,
652
+ status: 'oversize',
653
+ byteLength,
654
+ issues: [validationIssue('workflow-request-oversize', `Workflow request exceeds the ${WORKFLOW_REQUEST_MAX_BYTES}-byte UTF-8 limit`)],
655
+ };
656
+ }
657
+ let parsed;
658
+ try {
659
+ parsed = JSON.parse(raw);
660
+ }
661
+ catch {
662
+ return {
663
+ valid: false,
664
+ status: 'invalid',
665
+ byteLength,
666
+ issues: [validationIssue('malformed-workflow-request-json', 'Workflow request contains malformed JSON')],
667
+ };
668
+ }
669
+ return { ...validateWorkflowListRequest(parsed), byteLength };
670
+ }
671
+ function appendProseSegment(segments, content) {
672
+ if (!content)
673
+ return;
674
+ const previous = segments[segments.length - 1];
675
+ if (previous?.type === 'prose') {
676
+ previous.content += content;
677
+ }
678
+ else {
679
+ segments.push({ type: 'prose', content });
680
+ }
681
+ }
682
+ function consumeLineBreak(source, offset) {
683
+ if (source.startsWith('\r\n', offset))
684
+ return offset + 2;
685
+ if (source.startsWith('\n', offset))
686
+ return offset + 1;
687
+ return offset;
688
+ }
689
+ function stripFenceTrailingLineBreak(value) {
690
+ if (value.endsWith('\r\n'))
691
+ return value.slice(0, -2);
692
+ if (value.endsWith('\n'))
693
+ return value.slice(0, -1);
694
+ return value;
695
+ }
696
+ export function parseAgentChatArtifacts(source, options = {}) {
697
+ const segments = [];
698
+ const openingPattern = /^( {0,3})```(wiro-workflow-request|wiro-workflow)[ \t]*\r?$/gm;
699
+ const seenRequestIds = new Set(options.seenRequestIds ?? []);
700
+ const answerByteLength = utf8ByteLength(source);
701
+ let cursor = 0;
702
+ let streamState = 'complete';
703
+ let openingMatch;
704
+ while ((openingMatch = openingPattern.exec(source)) !== null) {
705
+ appendProseSegment(segments, source.slice(cursor, openingMatch.index));
706
+ const fenceType = openingMatch[2];
707
+ const bodyStart = consumeLineBreak(source, openingPattern.lastIndex);
708
+ const closingPattern = /^( {0,3})```[ \t]*\r?$/gm;
709
+ closingPattern.lastIndex = bodyStart;
710
+ const closingMatch = closingPattern.exec(source);
711
+ if (!closingMatch) {
712
+ const partialBody = source.slice(bodyStart);
713
+ const byteLength = utf8ByteLength(partialBody);
714
+ if (fenceType === WORKFLOW_REQUEST_FENCE) {
715
+ segments.push({
716
+ type: 'workflow-request',
717
+ status: byteLength > WORKFLOW_REQUEST_MAX_BYTES ? 'oversize' : 'incomplete',
718
+ byteLength,
719
+ issues: byteLength > WORKFLOW_REQUEST_MAX_BYTES ? [validationIssue('workflow-request-oversize', `Workflow request exceeds the ${WORKFLOW_REQUEST_MAX_BYTES}-byte UTF-8 limit`)] : [],
720
+ });
721
+ streamState = 'loading-workflows';
722
+ }
723
+ else {
724
+ segments.push({
725
+ type: 'workflow',
726
+ status: byteLength > WORKFLOW_ARTIFACT_MAX_BYTES ? 'oversize' : 'incomplete',
727
+ byteLength,
728
+ issues: byteLength > WORKFLOW_ARTIFACT_MAX_BYTES ? [validationIssue('workflow-artifact-oversize', `Workflow artifact exceeds the ${WORKFLOW_ARTIFACT_MAX_BYTES}-byte UTF-8 limit`)] : [],
729
+ });
730
+ streamState = 'building-workflow';
731
+ }
732
+ cursor = source.length;
733
+ break;
734
+ }
735
+ const raw = stripFenceTrailingLineBreak(source.slice(bodyStart, closingMatch.index));
736
+ if (fenceType === WORKFLOW_REQUEST_FENCE) {
737
+ const result = validateWorkflowListRequestJson(raw);
738
+ if (result.valid && result.value && seenRequestIds.has(result.value.requestId)) {
739
+ segments.push({
740
+ type: 'workflow-request',
741
+ status: 'invalid',
742
+ raw,
743
+ byteLength: result.byteLength ?? utf8ByteLength(raw),
744
+ issues: [validationIssue('duplicate-workflow-request-id', `requestId "${result.value.requestId}" has already been used`, '$.requestId')],
745
+ });
746
+ }
747
+ else {
748
+ if (result.value)
749
+ seenRequestIds.add(result.value.requestId);
750
+ segments.push({
751
+ type: 'workflow-request',
752
+ status: result.status,
753
+ request: result.value,
754
+ requestHash: result.value ? canonicalHash(result.value) : undefined,
755
+ raw,
756
+ byteLength: result.byteLength ?? utf8ByteLength(raw),
757
+ issues: result.issues,
758
+ });
759
+ }
760
+ }
761
+ else {
762
+ const result = validateWorkflowArtifactJson(raw);
763
+ segments.push({
764
+ type: 'workflow',
765
+ status: result.status,
766
+ artifact: result.value,
767
+ artifactHash: result.value ? canonicalHash(result.value) : undefined,
768
+ raw: result.value ? undefined : raw,
769
+ byteLength: result.byteLength ?? utf8ByteLength(raw),
770
+ issues: result.issues,
771
+ });
772
+ }
773
+ cursor = consumeLineBreak(source, closingPattern.lastIndex);
774
+ openingPattern.lastIndex = cursor;
775
+ }
776
+ if (cursor < source.length) {
777
+ appendProseSegment(segments, source.slice(cursor));
778
+ }
779
+ // Over the whole-answer cap nothing is actionable, but an artifact keeps its
780
+ // value so callers must check status and issues, never the value alone.
781
+ if (answerByteLength > WORKFLOW_ASSISTANT_MAX_BYTES) {
782
+ const issue = validationIssue('workflow-answer-oversize', `Workflow Agent answer exceeds the ${WORKFLOW_ASSISTANT_MAX_BYTES}-byte UTF-8 limit`);
783
+ for (const segment of segments) {
784
+ if (segment.type === 'prose')
785
+ continue;
786
+ segment.status = 'oversize';
787
+ segment.issues = [...segment.issues, issue];
788
+ if (segment.type === 'workflow-request')
789
+ segment.request = undefined;
790
+ }
791
+ }
792
+ const requests = segments.filter((segment) => segment.type === 'workflow-request');
793
+ const artifacts = segments.filter((segment) => segment.type === 'workflow');
794
+ const prose = segments
795
+ .filter((segment) => segment.type === 'prose')
796
+ .map(segment => segment.content)
797
+ .join('')
798
+ .trim();
799
+ return { prose, segments, requests, artifacts, streamState };
800
+ }
801
+ //# sourceMappingURL=workflow-artifacts.js.map