@wiro-ai/wiro-mcp 1.3.4 → 1.4.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.
- package/README.md +183 -16
- package/dist/client.d.ts +16 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +126 -24
- package/dist/client.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +29 -0
- package/dist/instructions.js.map +1 -0
- package/dist/playbooks.d.ts +8 -0
- package/dist/playbooks.d.ts.map +1 -0
- package/dist/playbooks.js +667 -0
- package/dist/playbooks.js.map +1 -0
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +133 -0
- package/dist/prompts.js.map +1 -0
- package/dist/server.d.ts +15 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +12 -7
- package/dist/server.js.map +1 -1
- package/dist/tools/apply-agent-skills.d.ts +4 -0
- package/dist/tools/apply-agent-skills.d.ts.map +1 -0
- package/dist/tools/apply-agent-skills.js +601 -0
- package/dist/tools/apply-agent-skills.js.map +1 -0
- package/dist/tools/cancel-agent-message.d.ts +4 -0
- package/dist/tools/cancel-agent-message.d.ts.map +1 -0
- package/dist/tools/cancel-agent-message.js +126 -0
- package/dist/tools/cancel-agent-message.js.map +1 -0
- package/dist/tools/cancel-task.js +3 -3
- package/dist/tools/cancel-task.js.map +1 -1
- package/dist/tools/clone-workflow.d.ts +4 -0
- package/dist/tools/clone-workflow.d.ts.map +1 -0
- package/dist/tools/clone-workflow.js +83 -0
- package/dist/tools/clone-workflow.js.map +1 -0
- package/dist/tools/connect-agent-oauth.d.ts +4 -0
- package/dist/tools/connect-agent-oauth.d.ts.map +1 -0
- package/dist/tools/connect-agent-oauth.js +508 -0
- package/dist/tools/connect-agent-oauth.js.map +1 -0
- package/dist/tools/control-agent.d.ts +4 -0
- package/dist/tools/control-agent.d.ts.map +1 -0
- package/dist/tools/control-agent.js +289 -0
- package/dist/tools/control-agent.js.map +1 -0
- package/dist/tools/create-workflow.d.ts +100 -0
- package/dist/tools/create-workflow.d.ts.map +1 -0
- package/dist/tools/create-workflow.js +102 -0
- package/dist/tools/create-workflow.js.map +1 -0
- package/dist/tools/delete-agent-session.d.ts +6 -0
- package/dist/tools/delete-agent-session.d.ts.map +1 -0
- package/dist/tools/delete-agent-session.js +134 -0
- package/dist/tools/delete-agent-session.js.map +1 -0
- package/dist/tools/delete-agent.d.ts +4 -0
- package/dist/tools/delete-agent.d.ts.map +1 -0
- package/dist/tools/delete-agent.js +145 -0
- package/dist/tools/delete-agent.js.map +1 -0
- package/dist/tools/delete-workflow.d.ts +4 -0
- package/dist/tools/delete-workflow.d.ts.map +1 -0
- package/dist/tools/delete-workflow.js +77 -0
- package/dist/tools/delete-workflow.js.map +1 -0
- package/dist/tools/deploy-agent.d.ts +4 -0
- package/dist/tools/deploy-agent.d.ts.map +1 -0
- package/dist/tools/deploy-agent.js +463 -0
- package/dist/tools/deploy-agent.js.map +1 -0
- package/dist/tools/disconnect-agent-oauth.d.ts +4 -0
- package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -0
- package/dist/tools/disconnect-agent-oauth.js +118 -0
- package/dist/tools/disconnect-agent-oauth.js.map +1 -0
- package/dist/tools/get-account.d.ts +4 -0
- package/dist/tools/get-account.d.ts.map +1 -0
- package/dist/tools/get-account.js +112 -0
- package/dist/tools/get-account.js.map +1 -0
- package/dist/tools/get-agent-activity.d.ts +4 -0
- package/dist/tools/get-agent-activity.d.ts.map +1 -0
- package/dist/tools/get-agent-activity.js +383 -0
- package/dist/tools/get-agent-activity.js.map +1 -0
- package/dist/tools/get-agent-conversation.d.ts +4 -0
- package/dist/tools/get-agent-conversation.d.ts.map +1 -0
- package/dist/tools/get-agent-conversation.js +228 -0
- package/dist/tools/get-agent-conversation.js.map +1 -0
- package/dist/tools/get-agent-message.d.ts +4 -0
- package/dist/tools/get-agent-message.d.ts.map +1 -0
- package/dist/tools/get-agent-message.js +68 -0
- package/dist/tools/get-agent-message.js.map +1 -0
- package/dist/tools/get-agent-template.d.ts +4 -0
- package/dist/tools/get-agent-template.d.ts.map +1 -0
- package/dist/tools/get-agent-template.js +128 -0
- package/dist/tools/get-agent-template.js.map +1 -0
- package/dist/tools/get-model-schema.d.ts +5 -1
- package/dist/tools/get-model-schema.d.ts.map +1 -1
- package/dist/tools/get-model-schema.js +138 -8
- package/dist/tools/get-model-schema.js.map +1 -1
- package/dist/tools/get-my-agent.d.ts +4 -0
- package/dist/tools/get-my-agent.d.ts.map +1 -0
- package/dist/tools/get-my-agent.js +117 -0
- package/dist/tools/get-my-agent.js.map +1 -0
- package/dist/tools/get-playbook.d.ts +3 -0
- package/dist/tools/get-playbook.d.ts.map +1 -0
- package/dist/tools/get-playbook.js +50 -0
- package/dist/tools/get-playbook.js.map +1 -0
- package/dist/tools/get-task-price.d.ts +1 -1
- package/dist/tools/get-task-price.d.ts.map +1 -1
- package/dist/tools/get-task-price.js +64 -16
- package/dist/tools/get-task-price.js.map +1 -1
- package/dist/tools/get-task.d.ts.map +1 -1
- package/dist/tools/get-task.js +26 -9
- package/dist/tools/get-task.js.map +1 -1
- package/dist/tools/get-workflow.d.ts +9 -0
- package/dist/tools/get-workflow.d.ts.map +1 -0
- package/dist/tools/get-workflow.js +105 -0
- package/dist/tools/get-workflow.js.map +1 -0
- package/dist/tools/index.d.ts +63 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +131 -14
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/kill-task.d.ts.map +1 -1
- package/dist/tools/kill-task.js +10 -6
- package/dist/tools/kill-task.js.map +1 -1
- package/dist/tools/list-agent-skills.d.ts +4 -0
- package/dist/tools/list-agent-skills.d.ts.map +1 -0
- package/dist/tools/list-agent-skills.js +242 -0
- package/dist/tools/list-agent-skills.js.map +1 -0
- package/dist/tools/list-agent-templates.d.ts +4 -0
- package/dist/tools/list-agent-templates.d.ts.map +1 -0
- package/dist/tools/list-agent-templates.js +110 -0
- package/dist/tools/list-agent-templates.js.map +1 -0
- package/dist/tools/list-my-agents.d.ts +4 -0
- package/dist/tools/list-my-agents.d.ts.map +1 -0
- package/dist/tools/list-my-agents.js +93 -0
- package/dist/tools/list-my-agents.js.map +1 -0
- package/dist/tools/list-studio-results.d.ts +4 -0
- package/dist/tools/list-studio-results.d.ts.map +1 -0
- package/dist/tools/list-studio-results.js +95 -0
- package/dist/tools/list-studio-results.js.map +1 -0
- package/dist/tools/list-tasks.d.ts.map +1 -1
- package/dist/tools/list-tasks.js +7 -5
- package/dist/tools/list-tasks.js.map +1 -1
- package/dist/tools/list-workflow-runs.d.ts +4 -0
- package/dist/tools/list-workflow-runs.d.ts.map +1 -0
- package/dist/tools/list-workflow-runs.js +110 -0
- package/dist/tools/list-workflow-runs.js.map +1 -0
- package/dist/tools/list-workflows.d.ts +4 -0
- package/dist/tools/list-workflows.d.ts.map +1 -0
- package/dist/tools/list-workflows.js +143 -0
- package/dist/tools/list-workflows.js.map +1 -0
- package/dist/tools/manage-agent-billing.d.ts +4 -0
- package/dist/tools/manage-agent-billing.d.ts.map +1 -0
- package/dist/tools/manage-agent-billing.js +788 -0
- package/dist/tools/manage-agent-billing.js.map +1 -0
- package/dist/tools/manage-agent-custom-skill.d.ts +4 -0
- package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -0
- package/dist/tools/manage-agent-custom-skill.js +531 -0
- package/dist/tools/manage-agent-custom-skill.js.map +1 -0
- package/dist/tools/recommend-model.d.ts.map +1 -1
- package/dist/tools/recommend-model.js +6 -5
- package/dist/tools/recommend-model.js.map +1 -1
- package/dist/tools/resume-workflow-run.d.ts +4 -0
- package/dist/tools/resume-workflow-run.d.ts.map +1 -0
- package/dist/tools/resume-workflow-run.js +69 -0
- package/dist/tools/resume-workflow-run.js.map +1 -0
- package/dist/tools/run-model.d.ts.map +1 -1
- package/dist/tools/run-model.js +40 -4
- package/dist/tools/run-model.js.map +1 -1
- package/dist/tools/run-workflow.d.ts +6 -0
- package/dist/tools/run-workflow.d.ts.map +1 -0
- package/dist/tools/run-workflow.js +132 -0
- package/dist/tools/run-workflow.js.map +1 -0
- package/dist/tools/save-workflow-from-agent.d.ts +4 -0
- package/dist/tools/save-workflow-from-agent.d.ts.map +1 -0
- package/dist/tools/save-workflow-from-agent.js +280 -0
- package/dist/tools/save-workflow-from-agent.js.map +1 -0
- package/dist/tools/schemas.d.ts +182 -115
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +26 -6
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/search-models.d.ts.map +1 -1
- package/dist/tools/search-models.js +9 -6
- package/dist/tools/search-models.js.map +1 -1
- package/dist/tools/send-agent-message.d.ts +4 -0
- package/dist/tools/send-agent-message.d.ts.map +1 -0
- package/dist/tools/send-agent-message.js +156 -0
- package/dist/tools/send-agent-message.js.map +1 -0
- package/dist/tools/set-agent-credentials.d.ts +4 -0
- package/dist/tools/set-agent-credentials.d.ts.map +1 -0
- package/dist/tools/set-agent-credentials.js +540 -0
- package/dist/tools/set-agent-credentials.js.map +1 -0
- package/dist/tools/update-agent.d.ts +4 -0
- package/dist/tools/update-agent.d.ts.map +1 -0
- package/dist/tools/update-agent.js +179 -0
- package/dist/tools/update-agent.js.map +1 -0
- package/dist/tools/update-workflow.d.ts +201 -0
- package/dist/tools/update-workflow.d.ts.map +1 -0
- package/dist/tools/update-workflow.js +168 -0
- package/dist/tools/update-workflow.js.map +1 -0
- package/dist/tools/upload-file.d.ts.map +1 -1
- package/dist/tools/upload-file.js +12 -7
- package/dist/tools/upload-file.js.map +1 -1
- package/dist/tools/wait-for-task.d.ts.map +1 -1
- package/dist/tools/wait-for-task.js +20 -2
- package/dist/tools/wait-for-task.js.map +1 -1
- package/dist/tools/wait-for-workflow-run.d.ts +259 -0
- package/dist/tools/wait-for-workflow-run.d.ts.map +1 -0
- package/dist/tools/wait-for-workflow-run.js +190 -0
- package/dist/tools/wait-for-workflow-run.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/agent-catalog.d.ts +1256 -0
- package/dist/utils/agent-catalog.d.ts.map +1 -0
- package/dist/utils/agent-catalog.js +387 -0
- package/dist/utils/agent-catalog.js.map +1 -0
- package/dist/utils/agent-config.d.ts +443 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +1676 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/agent-message.d.ts +112 -0
- package/dist/utils/agent-message.d.ts.map +1 -0
- package/dist/utils/agent-message.js +243 -0
- package/dist/utils/agent-message.js.map +1 -0
- package/dist/utils/agent-targets.d.ts +76 -0
- package/dist/utils/agent-targets.d.ts.map +1 -0
- package/dist/utils/agent-targets.js +412 -0
- package/dist/utils/agent-targets.js.map +1 -0
- package/dist/utils/agent-turns.d.ts +354 -0
- package/dist/utils/agent-turns.d.ts.map +1 -0
- package/dist/utils/agent-turns.js +660 -0
- package/dist/utils/agent-turns.js.map +1 -0
- package/dist/utils/api.d.ts +40 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/api.js +111 -0
- package/dist/utils/api.js.map +1 -0
- package/dist/utils/format.d.ts +65 -2
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +583 -81
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/json-schema-dialect.d.ts +8 -1
- package/dist/utils/json-schema-dialect.d.ts.map +1 -1
- package/dist/utils/json-schema-dialect.js +23 -1
- package/dist/utils/json-schema-dialect.js.map +1 -1
- package/dist/utils/media.d.ts +28 -0
- package/dist/utils/media.d.ts.map +1 -0
- package/dist/utils/media.js +65 -0
- package/dist/utils/media.js.map +1 -0
- package/dist/utils/poll.d.ts +39 -0
- package/dist/utils/poll.d.ts.map +1 -0
- package/dist/utils/poll.js +111 -0
- package/dist/utils/poll.js.map +1 -0
- package/dist/utils/remote-file.js +2 -2
- package/dist/utils/remote-file.js.map +1 -1
- package/dist/utils/structured.d.ts +4 -0
- package/dist/utils/structured.d.ts.map +1 -1
- package/dist/utils/structured.js +23 -7
- package/dist/utils/structured.js.map +1 -1
- package/dist/utils/studio-runs.d.ts +87 -0
- package/dist/utils/studio-runs.d.ts.map +1 -0
- package/dist/utils/studio-runs.js +167 -0
- package/dist/utils/studio-runs.js.map +1 -0
- package/dist/utils/user-agent.d.ts +1199 -0
- package/dist/utils/user-agent.d.ts.map +1 -0
- package/dist/utils/user-agent.js +897 -0
- package/dist/utils/user-agent.js.map +1 -0
- package/dist/utils/workflow-agent.d.ts +151 -0
- package/dist/utils/workflow-agent.d.ts.map +1 -0
- package/dist/utils/workflow-agent.js +292 -0
- package/dist/utils/workflow-agent.js.map +1 -0
- package/dist/utils/workflow-artifacts.d.ts +160 -0
- package/dist/utils/workflow-artifacts.d.ts.map +1 -0
- package/dist/utils/workflow-artifacts.js +801 -0
- package/dist/utils/workflow-artifacts.js.map +1 -0
- package/dist/utils/workflow-save.d.ts +120 -0
- package/dist/utils/workflow-save.d.ts.map +1 -0
- package/dist/utils/workflow-save.js +728 -0
- package/dist/utils/workflow-save.js.map +1 -0
- package/dist/utils/workflow.d.ts +564 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +712 -0
- package/dist/utils/workflow.js.map +1 -0
- package/dist/utils/workspace.d.ts +38 -0
- package/dist/utils/workspace.d.ts.map +1 -0
- package/dist/utils/workspace.js +90 -0
- package/dist/utils/workspace.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
import { WiroApiError } from '../client.js';
|
|
2
|
+
import { asString, isPlainObject, requireApiSuccess, toInteger, truncate, } from './api.js';
|
|
3
|
+
import { fullSlugOf, isStrictlyEmpty, looksLikeSecretValue, parseJsonValue, SENSITIVE_INPUT_KEY, workflowInputs, workflowOutputs, workflowState, workflowTags, workflowUrl, } from './workflow.js';
|
|
4
|
+
export class WorkflowPrepareError extends Error {
|
|
5
|
+
issues;
|
|
6
|
+
warnings;
|
|
7
|
+
constructor(issues, warnings) {
|
|
8
|
+
super(issues.map(issue => (issue.nodeId ? `${issue.nodeId}: ${issue.message}` : issue.message)).join('\n')
|
|
9
|
+
|| 'The workflow could not be prepared.');
|
|
10
|
+
this.name = 'WorkflowPrepareError';
|
|
11
|
+
this.issues = issues;
|
|
12
|
+
this.warnings = warnings;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class WorkflowConflictError extends Error {
|
|
16
|
+
currentUpdatetime;
|
|
17
|
+
constructor(message, currentUpdatetime) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'WorkflowConflictError';
|
|
20
|
+
this.currentUpdatetime = currentUpdatetime;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function toIssues(value) {
|
|
24
|
+
if (!Array.isArray(value))
|
|
25
|
+
return [];
|
|
26
|
+
return value.filter(isPlainObject).map(issue => ({
|
|
27
|
+
message: asString(issue.message) ?? 'Invalid workflow.',
|
|
28
|
+
...(asString(issue.code) ? { code: asString(issue.code) } : {}),
|
|
29
|
+
...(asString(issue.nodeId) ? { nodeId: asString(issue.nodeId) } : {}),
|
|
30
|
+
...(asString(issue.input) ? { input: asString(issue.input) } : {}),
|
|
31
|
+
...(typeof issue.bytes === 'number' && Number.isFinite(issue.bytes) ? { bytes: issue.bytes } : {}),
|
|
32
|
+
...(typeof issue.limit === 'number' && Number.isFinite(issue.limit) ? { limit: issue.limit } : {}),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
export async function prepareWorkflowContents(client, contents, mode, anchorNodeIds = [], signal) {
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = await client.post('/Workflow/Prepare', { contents, mode, ...(mode === 'apply' ? { anchorNodeIds } : {}) }, signal);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof WiroApiError && error.status === 404) {
|
|
42
|
+
throw new Error('This Wiro API does not support preparing workflows yet, so the workflow was not saved.');
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const warnings = toIssues(response.warnings);
|
|
47
|
+
if (response.result !== true || !isPlainObject(response.contents)) {
|
|
48
|
+
const issues = toIssues(response.issues);
|
|
49
|
+
if (issues.length === 0) {
|
|
50
|
+
issues.push(...toIssues(response.errors));
|
|
51
|
+
}
|
|
52
|
+
throw new WorkflowPrepareError(issues, warnings);
|
|
53
|
+
}
|
|
54
|
+
return { contents: response.contents, warnings };
|
|
55
|
+
}
|
|
56
|
+
// Prepare fills every model parameter a step leaves out with its catalog value,
|
|
57
|
+
// as the editor does on its canvas, and the caller is told what was filled.
|
|
58
|
+
const SAMPLE_FILE_PATH = /\/sampleinputs\//;
|
|
59
|
+
function isSampleFile(value) {
|
|
60
|
+
if (typeof value === 'string')
|
|
61
|
+
return SAMPLE_FILE_PATH.test(value);
|
|
62
|
+
return Array.isArray(value) && value.some(entry => typeof entry === 'string' && SAMPLE_FILE_PATH.test(entry));
|
|
63
|
+
}
|
|
64
|
+
function shortValue(value) {
|
|
65
|
+
if (typeof value === 'string')
|
|
66
|
+
return JSON.stringify(truncate(value, 60));
|
|
67
|
+
if (Array.isArray(value))
|
|
68
|
+
return `(${value.length} value${value.length === 1 ? '' : 's'})`;
|
|
69
|
+
return truncate(JSON.stringify(value) ?? '', 60);
|
|
70
|
+
}
|
|
71
|
+
function holdsReference(value) {
|
|
72
|
+
if (typeof value === 'string')
|
|
73
|
+
return value.startsWith('$input.') || value.startsWith('$node-');
|
|
74
|
+
return Array.isArray(value) && value.some(holdsReference);
|
|
75
|
+
}
|
|
76
|
+
/** One note per model step that Prepare filled from the catalog. */
|
|
77
|
+
export function filledDefaultNotes(submitted, prepared) {
|
|
78
|
+
const notes = [];
|
|
79
|
+
const before = isPlainObject(submitted.nodes) ? submitted.nodes : {};
|
|
80
|
+
const after = isPlainObject(prepared.nodes) ? prepared.nodes : {};
|
|
81
|
+
for (const [nodeId, node] of Object.entries(after)) {
|
|
82
|
+
if (!isPlainObject(node) || node.type !== 'run' || !isPlainObject(node.input))
|
|
83
|
+
continue;
|
|
84
|
+
if (typeof node.tool !== 'string' || node.tool.startsWith('workflow-utilities/'))
|
|
85
|
+
continue;
|
|
86
|
+
const original = before[nodeId];
|
|
87
|
+
const authored = isPlainObject(original) && isPlainObject(original.input) ? original.input : {};
|
|
88
|
+
const input = node.input;
|
|
89
|
+
// Prepare moves a reference wired into a legacy multi-file input (<x>Multiple) to
|
|
90
|
+
// its URL field (<x>UrlMultiple) and records the pair in metadata.multifileActiveParam.
|
|
91
|
+
// That value is the caller's own wiring, as is any reference: no default is one.
|
|
92
|
+
const moved = isPlainObject(node.metadata) && isPlainObject(node.metadata.multifileActiveParam)
|
|
93
|
+
? node.metadata.multifileActiveParam
|
|
94
|
+
: {};
|
|
95
|
+
const filled = Object.keys(input).filter(key => {
|
|
96
|
+
if (Object.prototype.hasOwnProperty.call(authored, key))
|
|
97
|
+
return false;
|
|
98
|
+
const parent = moved[key];
|
|
99
|
+
if (typeof parent === 'string' && Object.prototype.hasOwnProperty.call(authored, parent))
|
|
100
|
+
return false;
|
|
101
|
+
return !holdsReference(input[key]);
|
|
102
|
+
});
|
|
103
|
+
if (filled.length === 0)
|
|
104
|
+
continue;
|
|
105
|
+
const samples = filled.filter(key => key === 'prompt' || isSampleFile(input[key]));
|
|
106
|
+
const defaults = filled.filter(key => !samples.includes(key));
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (samples.length > 0) {
|
|
109
|
+
const described = samples.map(key => (key === 'prompt' && !isSampleFile(input[key])
|
|
110
|
+
? `\`${key}\` (the sample text ${shortValue(input[key])})`
|
|
111
|
+
: `\`${key}\` (sample files)`));
|
|
112
|
+
parts.push(`Wiro filled ${described.join(' and ')} from the model's page, and every run uses `
|
|
113
|
+
+ `${samples.length === 1 ? 'it' : 'them'} as is. Set your own value, or an empty one ("" or []) to leave a file input out.`);
|
|
114
|
+
}
|
|
115
|
+
if (defaults.length > 0) {
|
|
116
|
+
parts.push(`${samples.length > 0 ? 'Also filled' : 'Filled'} with the model's defaults: `
|
|
117
|
+
+ `${defaults.map(key => `\`${key}\` ${shortValue(input[key])}`).join(', ')}.`);
|
|
118
|
+
}
|
|
119
|
+
notes.push({ code: samples.length > 0 ? 'sample-defaults-filled' : 'defaults-filled', nodeId, message: parts.join(' ') });
|
|
120
|
+
}
|
|
121
|
+
return notes;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* One note per saved Start value or Start field default that looks like a
|
|
125
|
+
* provider key. The value itself is never shown.
|
|
126
|
+
*/
|
|
127
|
+
export function keyLikeValueNotes(contents) {
|
|
128
|
+
const notes = [];
|
|
129
|
+
const noKeyNeeded = 'Model steps on Wiro need no provider key.';
|
|
130
|
+
for (const [key, value] of Object.entries(savedStartValues(contents))) {
|
|
131
|
+
if (looksLikeSecretValue(value)) {
|
|
132
|
+
notes.push({
|
|
133
|
+
code: 'saved-value-looks-like-key',
|
|
134
|
+
message: `The saved value of \`${key}\` looks like a key. Everyone who can open this workflow can read saved `
|
|
135
|
+
+ `values, and copies keep them. ${noKeyNeeded}`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const fields = isPlainObject(contents.schema) && isPlainObject(contents.schema.input) ? contents.schema.input : {};
|
|
140
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
141
|
+
if (isPlainObject(field) && looksLikeSecretValue(field.defaultValue)) {
|
|
142
|
+
notes.push({
|
|
143
|
+
code: 'default-looks-like-key',
|
|
144
|
+
message: `The default of \`${key}\` looks like a key. Everyone who can open this workflow can read its `
|
|
145
|
+
+ `defaults, and copies keep them. ${noKeyNeeded}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return notes;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Start field parts the caller changed that Wiro saves the editor's way instead
|
|
153
|
+
* (a description, examples, required, name), as "`key` (part, part)".
|
|
154
|
+
*/
|
|
155
|
+
export function droppedStartFieldChanges(sent, prepared) {
|
|
156
|
+
const fieldsOf = (contents) => (isPlainObject(contents.schema) && isPlainObject(contents.schema.input)
|
|
157
|
+
? contents.schema.input
|
|
158
|
+
: {});
|
|
159
|
+
const after = fieldsOf(prepared);
|
|
160
|
+
const dropped = [];
|
|
161
|
+
for (const [key, field] of Object.entries(fieldsOf(sent))) {
|
|
162
|
+
const kept = after[key];
|
|
163
|
+
if (!isPlainObject(field) || !isPlainObject(kept))
|
|
164
|
+
continue;
|
|
165
|
+
const parts = Object.keys(field).filter(part => JSON.stringify(field[part]) !== JSON.stringify(kept[part]));
|
|
166
|
+
if (parts.length > 0)
|
|
167
|
+
dropped.push(`\`${key}\` (${parts.join(', ')})`);
|
|
168
|
+
}
|
|
169
|
+
return dropped;
|
|
170
|
+
}
|
|
171
|
+
/** The editor's title-to-slug rule; the server adds the 5-character id. */
|
|
172
|
+
export function titleToSlug(title) {
|
|
173
|
+
const slug = title
|
|
174
|
+
.toLowerCase()
|
|
175
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
176
|
+
.trim()
|
|
177
|
+
.replace(/\s+/g, '-')
|
|
178
|
+
.replace(/-+/g, '-')
|
|
179
|
+
.replace(/^-|-$/g, '')
|
|
180
|
+
.slice(0, 200)
|
|
181
|
+
.replace(/-$/, '');
|
|
182
|
+
return slug || `workflow-${Date.now().toString(36)}`;
|
|
183
|
+
}
|
|
184
|
+
export function tagProblem(tags) {
|
|
185
|
+
if (tags.length > 20)
|
|
186
|
+
return 'A workflow can have at most 20 tags.';
|
|
187
|
+
for (const tag of tags) {
|
|
188
|
+
if (!tag.trim())
|
|
189
|
+
return 'Tags cannot be empty.';
|
|
190
|
+
if (tag.trim().toLowerCase().startsWith('wiro-'))
|
|
191
|
+
return 'Tags that start with "wiro-" are reserved for Wiro.';
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
export async function createWorkflow(client, params, signal) {
|
|
196
|
+
const title = params.title.trim();
|
|
197
|
+
const prepared = await prepareWorkflowContents(client, params.contents, 'create', [], signal);
|
|
198
|
+
const body = {
|
|
199
|
+
slug: params.slug ? titleToSlug(params.slug) : titleToSlug(title),
|
|
200
|
+
title,
|
|
201
|
+
contents: JSON.stringify(prepared.contents),
|
|
202
|
+
};
|
|
203
|
+
if (params.description?.trim())
|
|
204
|
+
body.description = params.description.trim();
|
|
205
|
+
if (params.tags && params.tags.length > 0)
|
|
206
|
+
body.tags = JSON.stringify(params.tags.map(tag => tag.trim()));
|
|
207
|
+
const result = requireApiSuccess(await client.post('/Workflow/Add', body, signal), 'The workflow could not be created.');
|
|
208
|
+
const row = result.workflow?.[0] ?? {};
|
|
209
|
+
const fullSlug = fullSlugOf(row);
|
|
210
|
+
if (!fullSlug)
|
|
211
|
+
throw new Error('The workflow was created but its slug did not come back. Find it with list_workflows.');
|
|
212
|
+
const savedTitle = asString(row.title) ?? title;
|
|
213
|
+
return {
|
|
214
|
+
fullSlug,
|
|
215
|
+
url: workflowUrl(fullSlug),
|
|
216
|
+
title: savedTitle,
|
|
217
|
+
renamed: savedTitle !== title,
|
|
218
|
+
state: 'private',
|
|
219
|
+
...(asString(row.updatetime) ? { updatetime: asString(row.updatetime) } : {}),
|
|
220
|
+
inputs: workflowInputs(prepared.contents),
|
|
221
|
+
outputs: workflowOutputs(prepared.contents),
|
|
222
|
+
warnings: [
|
|
223
|
+
...prepared.warnings,
|
|
224
|
+
...filledDefaultNotes(params.contents, prepared.contents),
|
|
225
|
+
...keyLikeValueNotes(prepared.contents),
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Markers that stand in for a hidden value in a graph copied from a read or a
|
|
230
|
+
// Workflow Agent proposal. "[hidden]" is the mask this package shows in input
|
|
231
|
+
// summaries.
|
|
232
|
+
const HIDDEN_VALUES = new Set(['[REDACTED]', '[OMITTED]', '[hidden]']);
|
|
233
|
+
function collectHidden(value, path, found) {
|
|
234
|
+
if (typeof value === 'string') {
|
|
235
|
+
if (HIDDEN_VALUES.has(value))
|
|
236
|
+
found.push(path);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(value)) {
|
|
240
|
+
value.forEach((entry, index) => collectHidden(entry, `${path}[${index}]`, found));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (isPlainObject(value)) {
|
|
244
|
+
for (const [key, entry] of Object.entries(value))
|
|
245
|
+
collectHidden(entry, `${path}.${key}`, found);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Places in a graph that hold a hidden-secret marker instead of a value. */
|
|
249
|
+
export function findHiddenValues(contents) {
|
|
250
|
+
const found = [];
|
|
251
|
+
if (isPlainObject(contents.nodes)) {
|
|
252
|
+
for (const [nodeId, node] of Object.entries(contents.nodes)) {
|
|
253
|
+
if (isPlainObject(node))
|
|
254
|
+
collectHidden(node.input, `step ${nodeId} input`, found);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const schema = isPlainObject(contents.schema) ? contents.schema : {};
|
|
258
|
+
if (isPlainObject(schema.input)) {
|
|
259
|
+
for (const [key, field] of Object.entries(schema.input))
|
|
260
|
+
collectHidden(field, `Start field ${key}`, found);
|
|
261
|
+
}
|
|
262
|
+
const values = savedStartValues(contents);
|
|
263
|
+
for (const [key, value] of Object.entries(values))
|
|
264
|
+
collectHidden(value, `saved value of Start field ${key}`, found);
|
|
265
|
+
return found;
|
|
266
|
+
}
|
|
267
|
+
/** The Start values saved with a graph (metadata.input.values). */
|
|
268
|
+
function savedStartValues(contents) {
|
|
269
|
+
const metadata = isPlainObject(contents.metadata) ? contents.metadata : {};
|
|
270
|
+
const input = isPlainObject(metadata.input) ? metadata.input : {};
|
|
271
|
+
return isPlainObject(input.values) ? input.values : {};
|
|
272
|
+
}
|
|
273
|
+
function restoreHidden(next, stored, path) {
|
|
274
|
+
if (typeof next === 'string' && HIDDEN_VALUES.has(next)) {
|
|
275
|
+
if (stored === undefined) {
|
|
276
|
+
throw new Error(`${path} is hidden ("${next}") and the saved workflow has no value to restore there. Set it explicitly.`);
|
|
277
|
+
}
|
|
278
|
+
return JSON.parse(JSON.stringify(stored));
|
|
279
|
+
}
|
|
280
|
+
if (Array.isArray(next)) {
|
|
281
|
+
return next.map((entry, index) => restoreHidden(entry, Array.isArray(stored) ? stored[index] : undefined, `${path}[${index}]`));
|
|
282
|
+
}
|
|
283
|
+
if (isPlainObject(next)) {
|
|
284
|
+
return Object.fromEntries(Object.entries(next).map(([key, entry]) => [
|
|
285
|
+
key,
|
|
286
|
+
restoreHidden(entry, isPlainObject(stored) ? stored[key] : undefined, `${path}.${key}`),
|
|
287
|
+
]));
|
|
288
|
+
}
|
|
289
|
+
return next;
|
|
290
|
+
}
|
|
291
|
+
/** The step a "$node-<id>.<port>" reference reads, if it is one. */
|
|
292
|
+
function referencedStep(value) {
|
|
293
|
+
if (!value.startsWith('$node-'))
|
|
294
|
+
return undefined;
|
|
295
|
+
const dot = value.indexOf('.', 1);
|
|
296
|
+
return dot > 1 ? value.slice(1, dot) : undefined;
|
|
297
|
+
}
|
|
298
|
+
function readsValue(value, reference, carriers) {
|
|
299
|
+
if (typeof value === 'string') {
|
|
300
|
+
if (value === reference)
|
|
301
|
+
return true;
|
|
302
|
+
const step = referencedStep(value);
|
|
303
|
+
return step !== undefined && carriers.has(step);
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(value))
|
|
306
|
+
return value.some(entry => readsValue(entry, reference, carriers));
|
|
307
|
+
if (isPlainObject(value))
|
|
308
|
+
return Object.values(value).some(entry => readsValue(entry, reference, carriers));
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Every place that receives the value of $input.<key> when the workflow runs:
|
|
313
|
+
* "step (model) param" for a step input that reads it directly or through
|
|
314
|
+
* utility steps (they pass text on as it is), and "End field <name>" for a result.
|
|
315
|
+
*/
|
|
316
|
+
function startFieldConsumers(contents, key) {
|
|
317
|
+
const consumers = new Set();
|
|
318
|
+
const reference = `$input.${key}`;
|
|
319
|
+
if (!isPlainObject(contents.nodes))
|
|
320
|
+
return consumers;
|
|
321
|
+
const nodes = contents.nodes;
|
|
322
|
+
// Utility steps that carry the value, read directly or from another such step.
|
|
323
|
+
const carriers = new Set();
|
|
324
|
+
let grew;
|
|
325
|
+
do {
|
|
326
|
+
grew = false;
|
|
327
|
+
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
328
|
+
if (nodeId === 'output' || carriers.has(nodeId) || !isPlainObject(node))
|
|
329
|
+
continue;
|
|
330
|
+
if (typeof node.tool !== 'string' || !node.tool.startsWith('workflow-utilities/'))
|
|
331
|
+
continue;
|
|
332
|
+
if (readsValue(node.input, reference, carriers)) {
|
|
333
|
+
carriers.add(nodeId);
|
|
334
|
+
grew = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
} while (grew);
|
|
338
|
+
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
339
|
+
if (nodeId === 'output' || !isPlainObject(node) || !isPlainObject(node.input))
|
|
340
|
+
continue;
|
|
341
|
+
for (const [param, value] of Object.entries(node.input)) {
|
|
342
|
+
if (readsValue(value, reference, carriers))
|
|
343
|
+
consumers.add(`${nodeId} (${String(node.tool)}) ${param}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const end = nodes.output;
|
|
347
|
+
if (isPlainObject(end) && isPlainObject(end.fields)) {
|
|
348
|
+
for (const [field, value] of Object.entries(end.fields)) {
|
|
349
|
+
if (readsValue(value, reference, carriers))
|
|
350
|
+
consumers.add(`End field ${field}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return consumers;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Brings back what a graph written from a shared copy cannot carry: the stored
|
|
357
|
+
* Start values (kept only for fields that still exist) and every value that was
|
|
358
|
+
* hidden when the graph was shown to an agent. A step's hidden secret is only
|
|
359
|
+
* restored into the same model it was saved for, so a key never moves to another
|
|
360
|
+
* provider.
|
|
361
|
+
*/
|
|
362
|
+
export function restoreStoredValues(next, stored) {
|
|
363
|
+
const contents = JSON.parse(JSON.stringify(next));
|
|
364
|
+
const storedNodes = isPlainObject(stored.nodes) ? stored.nodes : {};
|
|
365
|
+
if (isPlainObject(contents.nodes)) {
|
|
366
|
+
for (const [nodeId, node] of Object.entries(contents.nodes)) {
|
|
367
|
+
if (!isPlainObject(node) || node.input === undefined)
|
|
368
|
+
continue;
|
|
369
|
+
const hidden = [];
|
|
370
|
+
collectHidden(node.input, `Step ${nodeId} input`, hidden);
|
|
371
|
+
if (hidden.length === 0)
|
|
372
|
+
continue;
|
|
373
|
+
const storedNode = storedNodes[nodeId];
|
|
374
|
+
if (!isPlainObject(storedNode)) {
|
|
375
|
+
throw new Error(`${hidden[0]} is hidden, but ${nodeId} is a new step, so the saved workflow has no value to restore there. Set it explicitly.`);
|
|
376
|
+
}
|
|
377
|
+
if (storedNode.tool !== node.tool) {
|
|
378
|
+
throw new Error(`${hidden[0]} is hidden and step ${nodeId} does not keep the same model as the saved workflow, so it cannot be restored. Set it explicitly.`);
|
|
379
|
+
}
|
|
380
|
+
node.input = restoreHidden(node.input, storedNode.input, `Step ${nodeId} input`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const schema = isPlainObject(contents.schema) ? contents.schema : {};
|
|
384
|
+
const fields = isPlainObject(schema.input) ? schema.input : {};
|
|
385
|
+
const storedSchema = isPlainObject(stored.schema) ? stored.schema : {};
|
|
386
|
+
const storedFields = isPlainObject(storedSchema.input) ? storedSchema.input : {};
|
|
387
|
+
// A hidden Start value is only restored while it still reaches the same step
|
|
388
|
+
// inputs (directly or through utility steps) and End fields it reached before,
|
|
389
|
+
// so a proposal cannot reroute it to another provider or into a result.
|
|
390
|
+
const refuseIfMoved = (key, hiddenPath) => {
|
|
391
|
+
const before = startFieldConsumers(stored, key);
|
|
392
|
+
const moved = [...startFieldConsumers(contents, key)].filter(consumer => !before.has(consumer));
|
|
393
|
+
if (moved.length > 0) {
|
|
394
|
+
throw new Error(`${hiddenPath} is hidden and the new steps use it in a different place (${moved.join(', ')}), so it cannot be restored. Set it explicitly.`);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
398
|
+
const hidden = [];
|
|
399
|
+
collectHidden(field, `Start field ${key}`, hidden);
|
|
400
|
+
if (hidden.length === 0)
|
|
401
|
+
continue;
|
|
402
|
+
refuseIfMoved(key, hidden[0]);
|
|
403
|
+
fields[key] = restoreHidden(field, storedFields[key], `Start field ${key}`);
|
|
404
|
+
}
|
|
405
|
+
const metadata = isPlainObject(contents.metadata) ? contents.metadata : (contents.metadata = {});
|
|
406
|
+
const inputMetadata = isPlainObject(metadata.input)
|
|
407
|
+
? metadata.input
|
|
408
|
+
: (metadata.input = { position: { x: 0, y: 0 } });
|
|
409
|
+
const storedValues = savedStartValues(stored);
|
|
410
|
+
if (!isPlainObject(inputMetadata.values)) {
|
|
411
|
+
// A graph without saved values (every Workflow Agent proposal) gets the stored
|
|
412
|
+
// ones back, but a secret one only while it reaches the same places.
|
|
413
|
+
const kept = {};
|
|
414
|
+
for (const [key, value] of Object.entries(storedValues)) {
|
|
415
|
+
if (!Object.prototype.hasOwnProperty.call(fields, key))
|
|
416
|
+
continue;
|
|
417
|
+
if ((SENSITIVE_INPUT_KEY.test(key) || looksLikeSecretValue(value)) && !isStrictlyEmpty(value)) {
|
|
418
|
+
refuseIfMoved(key, `Saved value of Start field ${key}`);
|
|
419
|
+
}
|
|
420
|
+
kept[key] = value;
|
|
421
|
+
}
|
|
422
|
+
if (Object.keys(kept).length > 0)
|
|
423
|
+
inputMetadata.values = kept;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
// Saved values come back hidden too, under a secret-looking Start field.
|
|
427
|
+
const values = inputMetadata.values;
|
|
428
|
+
for (const [key, value] of Object.entries(values)) {
|
|
429
|
+
const hidden = [];
|
|
430
|
+
collectHidden(value, `Saved value of Start field ${key}`, hidden);
|
|
431
|
+
if (hidden.length === 0)
|
|
432
|
+
continue;
|
|
433
|
+
refuseIfMoved(key, hidden[0]);
|
|
434
|
+
values[key] = restoreHidden(value, storedValues[key], `Saved value of Start field ${key}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return contents;
|
|
438
|
+
}
|
|
439
|
+
function describeStep(node) {
|
|
440
|
+
if (!isPlainObject(node))
|
|
441
|
+
return '';
|
|
442
|
+
return JSON.stringify({ tool: node.tool, depends: node.depends, input: node.input });
|
|
443
|
+
}
|
|
444
|
+
function withoutPositions(contents) {
|
|
445
|
+
const copy = JSON.parse(JSON.stringify(contents));
|
|
446
|
+
if (isPlainObject(copy.nodes)) {
|
|
447
|
+
for (const node of Object.values(copy.nodes)) {
|
|
448
|
+
if (isPlainObject(node) && isPlainObject(node.metadata))
|
|
449
|
+
delete node.metadata.position;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (isPlainObject(copy.metadata) && isPlainObject(copy.metadata.input))
|
|
453
|
+
delete copy.metadata.input.position;
|
|
454
|
+
return JSON.stringify(copy);
|
|
455
|
+
}
|
|
456
|
+
export function summarizeWorkflowChange(stored, next) {
|
|
457
|
+
if (!next) {
|
|
458
|
+
return {
|
|
459
|
+
stepsAdded: [], stepsRemoved: [], stepsChanged: [], inputsAdded: [], inputsRemoved: [],
|
|
460
|
+
inputsChanged: [], savedValuesChanged: [], outputsChanged: false, layoutOnly: false,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const storedNodes = isPlainObject(stored.nodes) ? stored.nodes : {};
|
|
464
|
+
const nextNodes = isPlainObject(next.nodes) ? next.nodes : {};
|
|
465
|
+
const steps = (nodes) => Object.keys(nodes).filter(id => id !== 'output');
|
|
466
|
+
const label = (nodes, id) => {
|
|
467
|
+
const node = nodes[id];
|
|
468
|
+
return isPlainObject(node) && typeof node.tool === 'string' ? `${id} (${node.tool})` : id;
|
|
469
|
+
};
|
|
470
|
+
const storedFields = isPlainObject(stored.schema) && isPlainObject(stored.schema.input) ? stored.schema.input : {};
|
|
471
|
+
const nextFields = isPlainObject(next.schema) && isPlainObject(next.schema.input) ? next.schema.input : {};
|
|
472
|
+
const values = (contents) => (isPlainObject(contents.metadata)
|
|
473
|
+
&& isPlainObject(contents.metadata.input) && isPlainObject(contents.metadata.input.values)
|
|
474
|
+
? contents.metadata.input.values
|
|
475
|
+
: {});
|
|
476
|
+
const storedValues = values(stored);
|
|
477
|
+
const nextValues = values(next);
|
|
478
|
+
return {
|
|
479
|
+
stepsAdded: steps(nextNodes).filter(id => !(id in storedNodes)).map(id => label(nextNodes, id)),
|
|
480
|
+
stepsRemoved: steps(storedNodes).filter(id => !(id in nextNodes)).map(id => label(storedNodes, id)),
|
|
481
|
+
stepsChanged: steps(nextNodes)
|
|
482
|
+
.filter(id => id in storedNodes && describeStep(storedNodes[id]) !== describeStep(nextNodes[id]))
|
|
483
|
+
.map(id => label(nextNodes, id)),
|
|
484
|
+
inputsAdded: Object.keys(nextFields).filter(key => !(key in storedFields)),
|
|
485
|
+
inputsRemoved: Object.keys(storedFields).filter(key => !(key in nextFields)),
|
|
486
|
+
inputsChanged: Object.keys(nextFields)
|
|
487
|
+
.filter(key => key in storedFields && JSON.stringify(storedFields[key]) !== JSON.stringify(nextFields[key])),
|
|
488
|
+
savedValuesChanged: [...new Set([...Object.keys(storedValues), ...Object.keys(nextValues)])]
|
|
489
|
+
.filter(key => JSON.stringify(storedValues[key]) !== JSON.stringify(nextValues[key])),
|
|
490
|
+
outputsChanged: JSON.stringify(workflowOutputs(stored)) !== JSON.stringify(workflowOutputs(next)),
|
|
491
|
+
layoutOnly: withoutPositions(stored) === withoutPositions(next),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
async function readEditableRow(client, fullSlug, signal) {
|
|
495
|
+
const detail = requireApiSuccess(await client.post('/Workflow/Detail', { slug: fullSlug }, signal), 'Workflow not found.');
|
|
496
|
+
const row = detail.workflow?.[0];
|
|
497
|
+
if (!row)
|
|
498
|
+
throw new Error('Workflow not found.');
|
|
499
|
+
if (!Object.prototype.hasOwnProperty.call(row, 'rejectreason')) {
|
|
500
|
+
throw new Error('Only the owner or the owner\'s team can edit this workflow. Clone it with clone_workflow to get your own copy.');
|
|
501
|
+
}
|
|
502
|
+
return row;
|
|
503
|
+
}
|
|
504
|
+
/** Posts once more, after a short pause, when the answer is "Workflow not found". */
|
|
505
|
+
export async function postWithNotFoundRetry(client, path, body, signal) {
|
|
506
|
+
const first = await client.post(path, body, signal);
|
|
507
|
+
const notFound = first.result !== true
|
|
508
|
+
&& (first.errors ?? []).some(error => /workflow not found/i.test(String(error?.message ?? '')));
|
|
509
|
+
if (!notFound || first.currentUpdatetime !== undefined)
|
|
510
|
+
return first;
|
|
511
|
+
await new Promise(resolve => setTimeout(resolve, 1_500));
|
|
512
|
+
return client.post(path, body, signal);
|
|
513
|
+
}
|
|
514
|
+
export async function updateWorkflow(client, params, signal) {
|
|
515
|
+
const row = await readEditableRow(client, params.fullSlug, signal);
|
|
516
|
+
const isProtected = toInteger(row.isprotected) === 1;
|
|
517
|
+
const isClone = row.clonedfromid !== undefined && row.clonedfromid !== null && row.clonedfromid !== '';
|
|
518
|
+
if (params.contents && isProtected && isClone) {
|
|
519
|
+
throw new Error('This is a copy of a protected workflow, so its steps cannot be edited. You can still change its title, description and tags.');
|
|
520
|
+
}
|
|
521
|
+
const storedDescription = asString(row.description) ?? '';
|
|
522
|
+
if (params.description !== undefined
|
|
523
|
+
&& storedDescription.length > 600
|
|
524
|
+
&& params.description.trim() === truncate(storedDescription, 600)) {
|
|
525
|
+
throw new Error('That description is a shortened copy of the saved one. Read the full description with get_workflow before editing it.');
|
|
526
|
+
}
|
|
527
|
+
const storedContents = parseJsonValue(row.contents);
|
|
528
|
+
const stored = isPlainObject(storedContents) ? storedContents : {};
|
|
529
|
+
const currentUpdatetime = asString(row.updatetime);
|
|
530
|
+
const stale = Boolean(params.loadedUpdatetime && currentUpdatetime
|
|
531
|
+
&& params.loadedUpdatetime !== currentUpdatetime && !params.force);
|
|
532
|
+
const conflict = () => new WorkflowConflictError('The workflow changed after you read it. Read it again with get_workflow and redo the change, '
|
|
533
|
+
+ 'or pass force: true only if the user wants to overwrite the newer version.', currentUpdatetime);
|
|
534
|
+
let contentsToSave;
|
|
535
|
+
let warnings = [];
|
|
536
|
+
let discarded = [];
|
|
537
|
+
if (params.contents) {
|
|
538
|
+
try {
|
|
539
|
+
const restored = restoreStoredValues(params.contents, stored);
|
|
540
|
+
// The saved graph sent back as it is needs no preparation, and one over
|
|
541
|
+
// Prepare's size limit (saved in the editor) could not get it.
|
|
542
|
+
if (JSON.stringify(restored) !== JSON.stringify(stored)) {
|
|
543
|
+
const nodes = isPlainObject(stored.nodes) ? Object.keys(stored.nodes) : [];
|
|
544
|
+
const anchors = ['input', ...nodes.filter(nodeId => nodeId !== 'output')];
|
|
545
|
+
const prepared = await prepareWorkflowContents(client, restored, 'apply', anchors, signal);
|
|
546
|
+
warnings = [
|
|
547
|
+
...prepared.warnings,
|
|
548
|
+
...filledDefaultNotes(restored, prepared.contents),
|
|
549
|
+
...keyLikeValueNotes(prepared.contents),
|
|
550
|
+
];
|
|
551
|
+
// An unchanged graph is not resent, so saving it again does not count as a change.
|
|
552
|
+
if (JSON.stringify(prepared.contents) !== JSON.stringify(stored))
|
|
553
|
+
contentsToSave = prepared.contents;
|
|
554
|
+
else
|
|
555
|
+
discarded = droppedStartFieldChanges(restored, prepared.contents);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
// Against a newer version, problems found while merging are a symptom of
|
|
560
|
+
// that version, so report the version conflict itself.
|
|
561
|
+
if (stale)
|
|
562
|
+
throw conflict();
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Only the caller's own tags are compared; Wiro's reserved tags are carried
|
|
567
|
+
// over as they are, so resending the same tags is not a change.
|
|
568
|
+
const isReserved = (tag) => tag.trim().toLowerCase().startsWith('wiro-');
|
|
569
|
+
const storedTags = workflowTags(row);
|
|
570
|
+
const regularStoredTags = storedTags.filter(tag => !isReserved(tag)).map(tag => tag.trim());
|
|
571
|
+
const nextRegularTags = params.tags?.map(tag => tag.trim());
|
|
572
|
+
const tagsChanged = nextRegularTags !== undefined
|
|
573
|
+
&& JSON.stringify(nextRegularTags) !== JSON.stringify(regularStoredTags);
|
|
574
|
+
const nextTags = tagsChanged ? [...nextRegularTags, ...storedTags.filter(isReserved)] : undefined;
|
|
575
|
+
const titleChanged = params.title !== undefined && params.title.trim() !== asString(row.title);
|
|
576
|
+
const descriptionChanged = params.description !== undefined && params.description.trim() !== storedDescription;
|
|
577
|
+
const anyChange = titleChanged || descriptionChanged || tagsChanged || contentsToSave !== undefined;
|
|
578
|
+
const state = workflowState(row);
|
|
579
|
+
const fullSlug = fullSlugOf(row) ?? params.fullSlug;
|
|
580
|
+
if (!anyChange) {
|
|
581
|
+
// Nothing to write, including when an earlier call already saved this change.
|
|
582
|
+
return {
|
|
583
|
+
fullSlug,
|
|
584
|
+
url: workflowUrl(fullSlug),
|
|
585
|
+
title: asString(row.title) ?? fullSlug,
|
|
586
|
+
renamed: false,
|
|
587
|
+
state,
|
|
588
|
+
...(currentUpdatetime ? { updatetime: currentUpdatetime } : {}),
|
|
589
|
+
inputs: workflowInputs(stored),
|
|
590
|
+
outputs: workflowOutputs(stored),
|
|
591
|
+
warnings,
|
|
592
|
+
reviewReset: false,
|
|
593
|
+
contentsChanged: false,
|
|
594
|
+
unchanged: true,
|
|
595
|
+
...(discarded.length > 0 ? { discarded } : {}),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
if (stale)
|
|
599
|
+
throw conflict();
|
|
600
|
+
const reviewReset = toInteger(row.ispublic) === 1;
|
|
601
|
+
const changes = {
|
|
602
|
+
...summarizeWorkflowChange(stored, contentsToSave),
|
|
603
|
+
...(titleChanged ? { titleChange: { from: asString(row.title) ?? '', to: params.title.trim() } } : {}),
|
|
604
|
+
descriptionChanged,
|
|
605
|
+
tagsChanged,
|
|
606
|
+
};
|
|
607
|
+
const savedContents = contentsToSave ?? stored;
|
|
608
|
+
if (params.preview) {
|
|
609
|
+
return {
|
|
610
|
+
fullSlug,
|
|
611
|
+
url: workflowUrl(fullSlug),
|
|
612
|
+
title: asString(row.title) ?? fullSlug,
|
|
613
|
+
renamed: false,
|
|
614
|
+
state,
|
|
615
|
+
...(currentUpdatetime ? { updatetime: currentUpdatetime } : {}),
|
|
616
|
+
inputs: workflowInputs(savedContents),
|
|
617
|
+
outputs: workflowOutputs(savedContents),
|
|
618
|
+
warnings,
|
|
619
|
+
reviewReset,
|
|
620
|
+
contentsChanged: contentsToSave !== undefined,
|
|
621
|
+
unchanged: false,
|
|
622
|
+
preview: true,
|
|
623
|
+
changes,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (reviewReset && !params.acknowledgeReviewReset) {
|
|
627
|
+
throw new Error('This workflow is published. Saving this change takes it out of the public gallery until Wiro reviews it again. '
|
|
628
|
+
+ 'Ask the user, then call again with acknowledge_review_reset: true.');
|
|
629
|
+
}
|
|
630
|
+
const body = { slug: fullSlug };
|
|
631
|
+
if (titleChanged)
|
|
632
|
+
body.title = params.title.trim();
|
|
633
|
+
if (descriptionChanged && params.description.trim())
|
|
634
|
+
body.description = params.description.trim();
|
|
635
|
+
if (nextTags)
|
|
636
|
+
body.tags = JSON.stringify(nextTags);
|
|
637
|
+
if (contentsToSave)
|
|
638
|
+
body.contents = JSON.stringify(contentsToSave);
|
|
639
|
+
const guard = params.loadedUpdatetime ?? currentUpdatetime;
|
|
640
|
+
if (guard)
|
|
641
|
+
body.loadedUpdatetime = guard;
|
|
642
|
+
if (params.force)
|
|
643
|
+
body.force = true;
|
|
644
|
+
const response = await postWithNotFoundRetry(client, '/Workflow/Update', body, signal);
|
|
645
|
+
if (response.result !== true) {
|
|
646
|
+
if (response.currentUpdatetime !== undefined) {
|
|
647
|
+
throw new WorkflowConflictError('Someone saved this workflow after you read it. Read it again with get_workflow and redo the change, '
|
|
648
|
+
+ 'or pass force: true only if the user wants to overwrite their version.', String(response.currentUpdatetime));
|
|
649
|
+
}
|
|
650
|
+
if ((response.errors ?? []).some(error => /workflow not found/i.test(String(error?.message ?? '')))) {
|
|
651
|
+
throw new Error('The workflow exists but could not be updated yet. Try again in a few seconds.');
|
|
652
|
+
}
|
|
653
|
+
requireApiSuccess(response, 'The workflow could not be updated.');
|
|
654
|
+
}
|
|
655
|
+
const updated = response.workflow?.[0] ?? {};
|
|
656
|
+
return {
|
|
657
|
+
fullSlug: fullSlugOf(updated) ?? fullSlug,
|
|
658
|
+
url: workflowUrl(fullSlugOf(updated) ?? fullSlug),
|
|
659
|
+
title: asString(updated.title) ?? asString(row.title) ?? fullSlug,
|
|
660
|
+
renamed: false,
|
|
661
|
+
state: reviewReset ? 'pending_review' : state,
|
|
662
|
+
...(asString(updated.updatetime) ? { updatetime: asString(updated.updatetime) } : {}),
|
|
663
|
+
inputs: workflowInputs(savedContents),
|
|
664
|
+
outputs: workflowOutputs(savedContents),
|
|
665
|
+
warnings,
|
|
666
|
+
reviewReset,
|
|
667
|
+
contentsChanged: contentsToSave !== undefined,
|
|
668
|
+
unchanged: false,
|
|
669
|
+
changes,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
export function formatWorkflowChanges(changes) {
|
|
673
|
+
const lines = [];
|
|
674
|
+
if (changes.titleChange)
|
|
675
|
+
lines.push(`- Title: "${changes.titleChange.from}" to "${changes.titleChange.to}"`);
|
|
676
|
+
if (changes.descriptionChanged)
|
|
677
|
+
lines.push('- Description changes');
|
|
678
|
+
if (changes.tagsChanged)
|
|
679
|
+
lines.push('- Tags change');
|
|
680
|
+
if (changes.stepsAdded.length > 0)
|
|
681
|
+
lines.push(`- Steps added: ${changes.stepsAdded.join(', ')}`);
|
|
682
|
+
if (changes.stepsRemoved.length > 0)
|
|
683
|
+
lines.push(`- Steps removed: ${changes.stepsRemoved.join(', ')}`);
|
|
684
|
+
if (changes.stepsChanged.length > 0)
|
|
685
|
+
lines.push(`- Steps changed: ${changes.stepsChanged.join(', ')}`);
|
|
686
|
+
if (changes.inputsAdded.length > 0)
|
|
687
|
+
lines.push(`- Inputs added: ${changes.inputsAdded.join(', ')}`);
|
|
688
|
+
if (changes.inputsRemoved.length > 0)
|
|
689
|
+
lines.push(`- Inputs removed: ${changes.inputsRemoved.join(', ')}`);
|
|
690
|
+
if (changes.inputsChanged.length > 0)
|
|
691
|
+
lines.push(`- Inputs changed (type, label or default): ${changes.inputsChanged.join(', ')}`);
|
|
692
|
+
if (changes.savedValuesChanged.length > 0)
|
|
693
|
+
lines.push(`- Saved input values change: ${changes.savedValuesChanged.join(', ')}`);
|
|
694
|
+
if (changes.outputsChanged)
|
|
695
|
+
lines.push('- Outputs change');
|
|
696
|
+
if (lines.length > 0)
|
|
697
|
+
return lines;
|
|
698
|
+
return [changes.layoutOnly ? '- Only step positions change' : '- Other graph changes'];
|
|
699
|
+
}
|
|
700
|
+
/** The most JSON a graph written outside the editor may have, defaults included. */
|
|
701
|
+
export const WORKFLOW_MAX_BYTES = 40 * 1024;
|
|
702
|
+
export function prepareIssuesText(error) {
|
|
703
|
+
const tooLarge = error.issues.find(issue => issue.code === 'contents-too-large' || issue.code === 'prepared-contents-too-large');
|
|
704
|
+
if (tooLarge) {
|
|
705
|
+
const size = tooLarge.bytes !== undefined ? ` (${(tooLarge.bytes / 1024).toFixed(1)} KB)` : '';
|
|
706
|
+
const cause = tooLarge.code === 'prepared-contents-too-large'
|
|
707
|
+
? ` It grew past the limit${size} when Wiro filled each model step's left-out inputs with their defaults: `
|
|
708
|
+
+ 'set those inputs yourself, or set a file input to [] to leave it out.'
|
|
709
|
+
: ` The graph sent was too large${size}.`;
|
|
710
|
+
return [
|
|
711
|
+
'## The workflow was not saved',
|
|
712
|
+
'',
|
|
713
|
+
`${tooLarge.message}${cause} A workflow written outside the wiro.ai editor can be at most ${WORKFLOW_MAX_BYTES / 1024} KB `
|
|
714
|
+
+ 'of JSON, counted after Wiro fills each step\'s defaults. Use fewer steps or shorter values. A larger '
|
|
715
|
+
+ 'workflow\'s steps can still be edited in the editor on wiro.ai, and its title, description and tags here.',
|
|
716
|
+
].join('\n');
|
|
717
|
+
}
|
|
718
|
+
const lines = ['## The workflow was not saved', '', 'Wiro found these problems in the workflow graph:', ''];
|
|
719
|
+
for (const issue of error.issues) {
|
|
720
|
+
// The End and Start nodes name their fields in `input` too.
|
|
721
|
+
const where = issue.nodeId === 'output' ? 'End' : issue.nodeId === 'input' ? 'Start' : issue.nodeId ? `\`${issue.nodeId}\`` : 'Workflow';
|
|
722
|
+
const part = !issue.input ? '' : issue.nodeId === 'output' || issue.nodeId === 'input' ? ` field \`${issue.input}\`` : ` input \`${issue.input}\``;
|
|
723
|
+
lines.push(`- ${where}${part}: ${issue.message}`);
|
|
724
|
+
}
|
|
725
|
+
lines.push('', 'Fix all of them and try again. Use get_model_schema to check a model\'s parameter ids and get_playbook topic "workflow-graph" for the graph format.');
|
|
726
|
+
return lines.join('\n');
|
|
727
|
+
}
|
|
728
|
+
//# sourceMappingURL=workflow-save.js.map
|