@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,712 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { asString, isPlainObject, toInteger, toNumber, truncate, unixToIso, } from './api.js';
|
|
3
|
+
import { formatPrice } from './format.js';
|
|
4
|
+
// Workflow rows and runs as the Wiro API returns them, projected to what an
|
|
5
|
+
// assistant needs: a workflow keeps its slug, title, inputs, outputs, state,
|
|
6
|
+
// counts and creator and team names, and a run its status, steps, cost and
|
|
7
|
+
// outputs, resolved from the End node the same way a run does.
|
|
8
|
+
export const WORKFLOW_PAGE_URL = 'https://wiro.ai/workflows/';
|
|
9
|
+
export const RUN_TERMINAL_STATUSES = ['completed', 'failed', 'cancelled', 'stopped'];
|
|
10
|
+
export class WorkflowInputError extends Error {
|
|
11
|
+
}
|
|
12
|
+
// What the workflow tools hide, and where. A secret-looking name matches
|
|
13
|
+
// SENSITIVE_INPUT_KEY; a key-looking value matches looksLikeSecretValue.
|
|
14
|
+
// - Input lists (get_workflow and the save results): a Start field's default and
|
|
15
|
+
// saved value show as "[hidden]" under a secret-looking name or when the value
|
|
16
|
+
// looks like a key, for every caller.
|
|
17
|
+
// - get_workflow contents, for every caller: a saved value (metadata.input.values)
|
|
18
|
+
// or Start field default under a secret-looking name or that looks like a key
|
|
19
|
+
// shows as "[hidden]". For a caller outside the workflow's workspace, values
|
|
20
|
+
// under a secret-looking key at any depth of a step's input also show as
|
|
21
|
+
// "[REDACTED]". "$" references are wiring, not values, and stay as they are.
|
|
22
|
+
// - wait_for_workflow_run debug_node_id: the inputs a step received, under a
|
|
23
|
+
// secret-looking key at any depth ("[hidden]").
|
|
24
|
+
// update_workflow restores "[hidden]" and "[REDACTED]" from the saved workflow.
|
|
25
|
+
export const SENSITIVE_INPUT_KEY = /(^|[-_])(password|secret|api[-_]?key|authorization|credentials?|private[-_]?key|access[-_]?token|refresh[-_]?token|client[-_]?secret|bearer[-_]?token|token)([-_]|$)/i;
|
|
26
|
+
// The shapes of common provider API keys, which a name check alone misses
|
|
27
|
+
// (an "openai_key" field, say).
|
|
28
|
+
const PROVIDER_KEY_SHAPES = [
|
|
29
|
+
/^sk-[A-Za-z0-9_-]{20,}$/, // OpenAI, Anthropic
|
|
30
|
+
/^AIza[0-9A-Za-z_-]{30,}$/, // Google API
|
|
31
|
+
/^(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}$/, // GitHub
|
|
32
|
+
/^github_pat_[A-Za-z0-9_]{20,}$/, // GitHub fine-grained
|
|
33
|
+
/^xox[abprs]-[A-Za-z0-9-]{10,}$/, // Slack
|
|
34
|
+
/^AKIA[0-9A-Z]{16}$/, // AWS access key id
|
|
35
|
+
/^hf_[A-Za-z0-9]{30,}$/, // Hugging Face
|
|
36
|
+
/^r8_[A-Za-z0-9]{30,}$/, // Replicate
|
|
37
|
+
/^(sk|rk)_(live|test)_[A-Za-z0-9]{16,}$/, // Stripe
|
|
38
|
+
];
|
|
39
|
+
/** True for a string shaped like a known provider's API key, whatever field holds it. */
|
|
40
|
+
export function looksLikeSecretValue(value) {
|
|
41
|
+
if (typeof value !== 'string')
|
|
42
|
+
return false;
|
|
43
|
+
const text = value.trim();
|
|
44
|
+
return PROVIDER_KEY_SHAPES.some(shape => shape.test(text));
|
|
45
|
+
}
|
|
46
|
+
function isFilledValue(value) {
|
|
47
|
+
return value !== '' && value !== null && value !== undefined;
|
|
48
|
+
}
|
|
49
|
+
/** Whether a Start field's default or saved value is shown as "[hidden]". */
|
|
50
|
+
function hidesStartValue(key, value) {
|
|
51
|
+
return isFilledValue(value) && (looksLikeSecretValue(value) || SENSITIVE_INPUT_KEY.test(key));
|
|
52
|
+
}
|
|
53
|
+
/** Accepts '<slug>-<slugid>' or a wiro.ai workflow URL and returns the full slug. */
|
|
54
|
+
export function parseWorkflowSlug(input) {
|
|
55
|
+
let value = input.trim();
|
|
56
|
+
if (/^https?:\/\//i.test(value)) {
|
|
57
|
+
try {
|
|
58
|
+
const url = new URL(value);
|
|
59
|
+
value = url.pathname.split('/').filter(Boolean).pop() ?? '';
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new WorkflowInputError(`"${input}" is not a valid workflow URL.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
value = decodeURIComponent(value.split(/[?#]/)[0] ?? '');
|
|
66
|
+
const separator = value.lastIndexOf('-');
|
|
67
|
+
const slugid = separator > 0 ? value.slice(separator + 1) : '';
|
|
68
|
+
if (value.length < 7 || separator <= 0 || !/^[A-Za-z0-9]{5}$/.test(slugid)) {
|
|
69
|
+
throw new WorkflowInputError(`"${input}" is not a full workflow slug. Use the "fullSlug" from list_workflows `
|
|
70
|
+
+ 'or get_workflow (it ends with a dash and a 5-character id), or the workflow page URL.');
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
export function fullSlugOf(row) {
|
|
75
|
+
const slug = asString(row.slug);
|
|
76
|
+
const slugid = asString(row.slugid);
|
|
77
|
+
return slug && slugid ? `${slug}-${slugid}` : undefined;
|
|
78
|
+
}
|
|
79
|
+
export function workflowUrl(fullSlug) {
|
|
80
|
+
return `${WORKFLOW_PAGE_URL}${fullSlug}`;
|
|
81
|
+
}
|
|
82
|
+
export function parseJsonValue(value) {
|
|
83
|
+
if (typeof value !== 'string')
|
|
84
|
+
return value;
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(value);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function workflowTags(row) {
|
|
93
|
+
const tags = parseJsonValue(row.tags);
|
|
94
|
+
return Array.isArray(tags) ? tags.filter((tag) => typeof tag === 'string') : [];
|
|
95
|
+
}
|
|
96
|
+
export function workflowState(row) {
|
|
97
|
+
if (toInteger(row.ispublic) !== 1)
|
|
98
|
+
return 'private';
|
|
99
|
+
const status = toInteger(row.status, 1);
|
|
100
|
+
if (status === 2)
|
|
101
|
+
return 'published';
|
|
102
|
+
if (status === 3)
|
|
103
|
+
return 'rejected';
|
|
104
|
+
return 'pending_review';
|
|
105
|
+
}
|
|
106
|
+
export const workflowInputSchema = z.object({
|
|
107
|
+
key: z.string(),
|
|
108
|
+
label: z.string(),
|
|
109
|
+
type: z.string(),
|
|
110
|
+
field: z.string().optional(),
|
|
111
|
+
options: z.array(z.object({ value: z.string(), text: z.string() })).optional(),
|
|
112
|
+
default: z.unknown().optional(),
|
|
113
|
+
savedValue: z.unknown().optional(),
|
|
114
|
+
});
|
|
115
|
+
/** The Start fields a run takes, keyed exactly as /Workflow/Run expects. */
|
|
116
|
+
export function workflowInputs(contents) {
|
|
117
|
+
if (!isPlainObject(contents))
|
|
118
|
+
return [];
|
|
119
|
+
const schema = isPlainObject(contents.schema) ? contents.schema : {};
|
|
120
|
+
const fields = isPlainObject(schema.input) ? schema.input : {};
|
|
121
|
+
const metadata = isPlainObject(contents.metadata) ? contents.metadata : {};
|
|
122
|
+
const inputMetadata = isPlainObject(metadata.input) ? metadata.input : {};
|
|
123
|
+
const values = isPlainObject(inputMetadata.values) ? inputMetadata.values : {};
|
|
124
|
+
return Object.entries(fields).map(([key, rawField]) => {
|
|
125
|
+
const field = isPlainObject(rawField) ? rawField : {};
|
|
126
|
+
const ui = isPlainObject(field.ui) ? field.ui : {};
|
|
127
|
+
const options = Array.isArray(ui.options)
|
|
128
|
+
? ui.options
|
|
129
|
+
.filter(isPlainObject)
|
|
130
|
+
.map(option => ({
|
|
131
|
+
value: String(option.value ?? ''),
|
|
132
|
+
text: String(option.text ?? option.value ?? ''),
|
|
133
|
+
}))
|
|
134
|
+
: undefined;
|
|
135
|
+
// A summary is for showing the inputs, so a secret-looking field's value and
|
|
136
|
+
// a value that looks like a key are masked here, for every caller.
|
|
137
|
+
const shown = (value) => (hidesStartValue(key, value) ? '[hidden]' : value);
|
|
138
|
+
return {
|
|
139
|
+
key,
|
|
140
|
+
label: asString(field.label) || asString(field.name) || key,
|
|
141
|
+
type: asString(field.type) || 'string',
|
|
142
|
+
...(asString(ui.field) ? { field: asString(ui.field) } : {}),
|
|
143
|
+
...(options && options.length > 0 ? { options } : {}),
|
|
144
|
+
...(Object.prototype.hasOwnProperty.call(field, 'defaultValue') ? { default: shown(field.defaultValue) } : {}),
|
|
145
|
+
...(Object.prototype.hasOwnProperty.call(values, key) ? { savedValue: shown(values[key]) } : {}),
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** End node fields: output label to reference. */
|
|
150
|
+
export function workflowOutputs(contents) {
|
|
151
|
+
if (!isPlainObject(contents) || !isPlainObject(contents.nodes))
|
|
152
|
+
return {};
|
|
153
|
+
const end = contents.nodes.output;
|
|
154
|
+
return isPlainObject(end) && isPlainObject(end.fields) ? { ...end.fields } : {};
|
|
155
|
+
}
|
|
156
|
+
export function workflowStepCount(contents) {
|
|
157
|
+
if (!isPlainObject(contents) || !isPlainObject(contents.nodes))
|
|
158
|
+
return 0;
|
|
159
|
+
return Object.keys(contents.nodes).filter(key => key !== 'output').length;
|
|
160
|
+
}
|
|
161
|
+
export const workflowSummarySchema = z.object({
|
|
162
|
+
fullSlug: z.string(),
|
|
163
|
+
url: z.string(),
|
|
164
|
+
title: z.string(),
|
|
165
|
+
description: z.string().optional(),
|
|
166
|
+
tags: z.array(z.string()),
|
|
167
|
+
state: z.enum(['private', 'pending_review', 'published', 'rejected']).optional(),
|
|
168
|
+
rejectReason: z.string().optional(),
|
|
169
|
+
isProtected: z.boolean(),
|
|
170
|
+
isClone: z.boolean().optional(),
|
|
171
|
+
stepCount: z.number().optional(),
|
|
172
|
+
toolCount: z.number().optional(),
|
|
173
|
+
models: z.array(z.string()).optional(),
|
|
174
|
+
creator: z.string().optional(),
|
|
175
|
+
team: z.string().optional(),
|
|
176
|
+
runCount: z.number().optional()
|
|
177
|
+
.describe('Runs while the workflow was in the public gallery, by anyone. Left out when there are none; the owner\'s runs of a private workflow are in list_workflow_runs.'),
|
|
178
|
+
likeCount: z.number().optional(),
|
|
179
|
+
cloneCount: z.number().optional(),
|
|
180
|
+
ratingAverage: z.number().optional(),
|
|
181
|
+
ratingCount: z.number().optional(),
|
|
182
|
+
commentCount: z.number().optional(),
|
|
183
|
+
cover: z.string().optional(),
|
|
184
|
+
createdAt: z.string().optional(),
|
|
185
|
+
updatedAt: z.string().optional(),
|
|
186
|
+
updatetime: z.string().optional(),
|
|
187
|
+
});
|
|
188
|
+
function optionalNumber(value) {
|
|
189
|
+
const parsed = toNumber(value);
|
|
190
|
+
return parsed === null ? undefined : parsed;
|
|
191
|
+
}
|
|
192
|
+
export function toWorkflowSummary(row, options = {}) {
|
|
193
|
+
const fullSlug = fullSlugOf(row) ?? '';
|
|
194
|
+
const user = isPlainObject(row.user) ? row.user : undefined;
|
|
195
|
+
const models = Array.isArray(row.tools)
|
|
196
|
+
? row.tools.filter((tool) => typeof tool === 'string')
|
|
197
|
+
: undefined;
|
|
198
|
+
const description = asString(row.description);
|
|
199
|
+
const rejectReason = asString(row.rejectreason);
|
|
200
|
+
return {
|
|
201
|
+
fullSlug,
|
|
202
|
+
url: workflowUrl(fullSlug),
|
|
203
|
+
title: asString(row.title) ?? fullSlug,
|
|
204
|
+
...(description ? { description: truncate(description, 600) } : {}),
|
|
205
|
+
tags: workflowTags(row),
|
|
206
|
+
...(options.includeState ? { state: workflowState(row) } : {}),
|
|
207
|
+
...(options.includeState && rejectReason ? { rejectReason } : {}),
|
|
208
|
+
isProtected: toInteger(row.isprotected) === 1,
|
|
209
|
+
...(row.clonedfromid !== undefined ? { isClone: row.clonedfromid !== null && row.clonedfromid !== '' } : {}),
|
|
210
|
+
...(optionalNumber(row.stepcount) !== undefined ? { stepCount: optionalNumber(row.stepcount) } : {}),
|
|
211
|
+
...(optionalNumber(row.toolcount) !== undefined ? { toolCount: optionalNumber(row.toolcount) } : {}),
|
|
212
|
+
...(models ? { models } : {}),
|
|
213
|
+
...(user && asString(user.username) ? { creator: asString(user.username) } : {}),
|
|
214
|
+
...(asString(row.teamname) ? { team: asString(row.teamname) } : {}),
|
|
215
|
+
// Wiro counts runs only while a workflow is in the public gallery, so the
|
|
216
|
+
// owner's runs of a private workflow or a copy are not in it.
|
|
217
|
+
...((optionalNumber(row.runcount) ?? 0) > 0 ? { runCount: optionalNumber(row.runcount) } : {}),
|
|
218
|
+
...(optionalNumber(row.likecount) !== undefined ? { likeCount: optionalNumber(row.likecount) } : {}),
|
|
219
|
+
...(optionalNumber(row.clonecount) !== undefined ? { cloneCount: optionalNumber(row.clonecount) } : {}),
|
|
220
|
+
...(optionalNumber(row.ratingaverage) !== undefined ? { ratingAverage: optionalNumber(row.ratingaverage) } : {}),
|
|
221
|
+
...(optionalNumber(row.ratingcount) !== undefined ? { ratingCount: optionalNumber(row.ratingcount) } : {}),
|
|
222
|
+
...(optionalNumber(row.commentcount) !== undefined ? { commentCount: optionalNumber(row.commentcount) } : {}),
|
|
223
|
+
...(asString(row.cover) ? { cover: asString(row.cover) } : {}),
|
|
224
|
+
...(unixToIso(row.createtime) ? { createdAt: unixToIso(row.createtime) } : {}),
|
|
225
|
+
...(unixToIso(row.updatetime) ? { updatedAt: unixToIso(row.updatetime) } : {}),
|
|
226
|
+
...(asString(row.updatetime) ? { updatetime: asString(row.updatetime) } : {}),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/** Masks, in place, the Start field defaults and saved values under a secret-looking name or that look like a key. */
|
|
230
|
+
function maskStartValues(contents) {
|
|
231
|
+
const schema = isPlainObject(contents.schema) ? contents.schema : {};
|
|
232
|
+
if (isPlainObject(schema.input)) {
|
|
233
|
+
for (const [key, field] of Object.entries(schema.input)) {
|
|
234
|
+
if (isPlainObject(field) && hidesStartValue(key, field.defaultValue))
|
|
235
|
+
field.defaultValue = '[hidden]';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const metadata = isPlainObject(contents.metadata) ? contents.metadata : {};
|
|
239
|
+
const inputMetadata = isPlainObject(metadata.input) ? metadata.input : {};
|
|
240
|
+
if (isPlainObject(inputMetadata.values)) {
|
|
241
|
+
const values = inputMetadata.values;
|
|
242
|
+
for (const [key, value] of Object.entries(values)) {
|
|
243
|
+
if (hidesStartValue(key, value))
|
|
244
|
+
values[key] = '[hidden]';
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** A step input with every value under a secret-looking key blanked, at any depth. */
|
|
249
|
+
function redactSecretKeys(value, secret = false) {
|
|
250
|
+
if (typeof value === 'string')
|
|
251
|
+
return secret && !value.startsWith('$') ? '[REDACTED]' : value;
|
|
252
|
+
if (Array.isArray(value))
|
|
253
|
+
return value.map(entry => redactSecretKeys(entry, secret));
|
|
254
|
+
if (isPlainObject(value)) {
|
|
255
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
256
|
+
key,
|
|
257
|
+
redactSecretKeys(entry, secret || SENSITIVE_INPUT_KEY.test(key)),
|
|
258
|
+
]));
|
|
259
|
+
}
|
|
260
|
+
return secret && value !== null && value !== undefined ? '[REDACTED]' : value;
|
|
261
|
+
}
|
|
262
|
+
/** The graph as a caller outside the workflow's workspace sees it. */
|
|
263
|
+
function redactNodeInputs(contents) {
|
|
264
|
+
const copy = JSON.parse(JSON.stringify(contents));
|
|
265
|
+
if (isPlainObject(copy.nodes)) {
|
|
266
|
+
for (const node of Object.values(copy.nodes)) {
|
|
267
|
+
if (isPlainObject(node) && node.input !== undefined)
|
|
268
|
+
node.input = redactSecretKeys(node.input);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
maskStartValues(copy);
|
|
272
|
+
return copy;
|
|
273
|
+
}
|
|
274
|
+
/** The graph as the workflow's own workspace sees it: secret Start values are hidden, step inputs are not. */
|
|
275
|
+
function hideStartValues(contents) {
|
|
276
|
+
const copy = JSON.parse(JSON.stringify(contents));
|
|
277
|
+
maskStartValues(copy);
|
|
278
|
+
return copy;
|
|
279
|
+
}
|
|
280
|
+
export const workflowDetailSchema = workflowSummarySchema.extend({
|
|
281
|
+
insider: z.boolean(),
|
|
282
|
+
canEditGraph: z.boolean(),
|
|
283
|
+
graphHidden: z.boolean(),
|
|
284
|
+
ownerInactive: z.boolean().optional(),
|
|
285
|
+
inputs: z.array(workflowInputSchema),
|
|
286
|
+
outputs: z.record(z.string(), z.unknown()),
|
|
287
|
+
estimatedCost: z.object({
|
|
288
|
+
type: z.string().optional(),
|
|
289
|
+
valueUsd: z.number().nullable().optional(),
|
|
290
|
+
notes: z.array(z.string()).optional()
|
|
291
|
+
.describe('What the figure leaves out or counts only in part: steps charged after the run by usage or without a fixed price for their settings, steps counted as one unit of a per-unit price or at a default, and inputs that change the price.'),
|
|
292
|
+
}).optional(),
|
|
293
|
+
contents: z.record(z.string(), z.unknown()).optional(),
|
|
294
|
+
});
|
|
295
|
+
// What one unit of a per-unit price is, by the unit Wiro names in the estimate.
|
|
296
|
+
const ESTIMATE_UNITS = {
|
|
297
|
+
'video-second': { per: 'per second of output video', unit: 'second' },
|
|
298
|
+
'audio-second': { per: 'per second of input audio', unit: 'second' },
|
|
299
|
+
'run-second': { per: 'per second of run time', unit: 'second' },
|
|
300
|
+
token: { per: 'per token', unit: 'token' },
|
|
301
|
+
character: { per: 'per character', unit: 'character' },
|
|
302
|
+
};
|
|
303
|
+
// Notes on a workflow's estimate, from the lines Wiro sends with it: steps the
|
|
304
|
+
// figure leaves out, steps it counts as one unit or at a default, and inputs
|
|
305
|
+
// that change the price.
|
|
306
|
+
function estimateNotes(details) {
|
|
307
|
+
const notes = new Set();
|
|
308
|
+
const usd = (value) => {
|
|
309
|
+
const amount = toNumber(value);
|
|
310
|
+
return amount === null ? null : `$${formatPrice(amount)}`;
|
|
311
|
+
};
|
|
312
|
+
for (const detail of Array.isArray(details) ? details : []) {
|
|
313
|
+
if (!isPlainObject(detail))
|
|
314
|
+
continue;
|
|
315
|
+
const unit = asString(detail.unit);
|
|
316
|
+
const rate = usd(detail.rate);
|
|
317
|
+
const amount = usd(detail.amount);
|
|
318
|
+
const low = usd(detail.rangemin);
|
|
319
|
+
const high = usd(detail.rangemax);
|
|
320
|
+
const field = asString(detail.startfield);
|
|
321
|
+
const input = field ? `the \`${field}\` input` : 'an input';
|
|
322
|
+
if (detail.code === 'metered') {
|
|
323
|
+
const rates = typeof detail.note === 'string' ? detail.note.replace(/\s+/g, ' ').trim() : '';
|
|
324
|
+
notes.add(`A step charged after the run by usage is not in this figure${rates ? `. Its rates: ${rates}` : '.'}`);
|
|
325
|
+
}
|
|
326
|
+
else if (detail.code === 'no-price') {
|
|
327
|
+
notes.add('A step without a fixed price for its settings is not in this figure: its charge follows the time it takes.');
|
|
328
|
+
}
|
|
329
|
+
else if (detail.code === 'choose-setting') {
|
|
330
|
+
const setting = asString(detail.paramlabel) || asString(detail.param);
|
|
331
|
+
const range = low === null || high === null ? '' : low === high ? `, ${low},` : `, ${low} to ${high},`;
|
|
332
|
+
notes.add(`A step whose price depends on ${setting ? `its ${setting} setting` : 'a setting'}${range} is not in this figure.`);
|
|
333
|
+
}
|
|
334
|
+
else if (detail.code === 'per-unit') {
|
|
335
|
+
const words = unit ? ESTIMATE_UNITS[unit] : undefined;
|
|
336
|
+
if (words && rate)
|
|
337
|
+
notes.add(`A step priced at ${rate} ${words.per} counts as one ${words.unit} in this figure; it is charged for every ${words.unit}.`);
|
|
338
|
+
}
|
|
339
|
+
else if (detail.code === 'start-field') {
|
|
340
|
+
if (unit === 'image') {
|
|
341
|
+
if (low && high)
|
|
342
|
+
notes.add(`Each image in ${input} adds ${low === high ? low : `${low} to ${high}`} by its size, which is not in this figure.`);
|
|
343
|
+
}
|
|
344
|
+
else if (unit === 'character') {
|
|
345
|
+
if (rate)
|
|
346
|
+
notes.add(`A step priced at ${rate} per character of ${input} counts its ${toInteger(detail.defaultlength)}-character default in this figure${amount ? ` (${amount})` : ''}; other text costs more or less.`);
|
|
347
|
+
}
|
|
348
|
+
else if (unit === 'audio-second') {
|
|
349
|
+
if (rate)
|
|
350
|
+
notes.add(`A step priced at ${rate} per second of the audio in ${input} counts as one second in this figure; it is charged for every second.`);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
const options = Array.isArray(detail.options)
|
|
354
|
+
? detail.options.filter(isPlainObject).map(option => `"${asString(option.value) ?? ''}" ${usd(option.price) ?? ''}`.trim())
|
|
355
|
+
: [];
|
|
356
|
+
const at = (price, value) => (asString(value) ? `${price} for "${asString(value)}"` : price);
|
|
357
|
+
const prices = options.length > 0
|
|
358
|
+
? `: ${options.join(', ')}`
|
|
359
|
+
: low && high ? `: ${low === high ? low : `${at(low, detail.rangeminvalue)} to ${at(high, detail.rangemaxvalue)}`}` : '';
|
|
360
|
+
const value = asString(detail.defaultvalue) ?? '';
|
|
361
|
+
const used = value === ''
|
|
362
|
+
? 'With no value set, that step is not in this figure.'
|
|
363
|
+
: amount
|
|
364
|
+
? `The figure uses "${value}" (${amount}).`
|
|
365
|
+
: `The figure uses "${value}", which has no price, so that step is not in it.`;
|
|
366
|
+
notes.add(`${field ? `The \`${field}\` input` : 'An input'} changes the price of a step${prices}. ${used}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else if (detail.code === 'earlier-step') {
|
|
370
|
+
if (unit === 'character') {
|
|
371
|
+
if (rate)
|
|
372
|
+
notes.add(`A step priced at ${rate} per character of text from an earlier step counts its ${toInteger(detail.defaultlength)}-character default text in this figure${amount ? ` (${amount})` : ''}; the real text costs more or less.`);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
const setting = asString(detail.paramlabel) || asString(detail.param);
|
|
376
|
+
const value = asString(detail.defaultvalue);
|
|
377
|
+
const counted = value ? ` (${value}${amount ? `, ${amount}` : ''})` : amount ? ` (${amount})` : '';
|
|
378
|
+
notes.add(`A step whose ${setting ? `${setting} setting` : 'price setting'} comes from an earlier step counts at its default${counted} in this figure; it can cost ${low && high && low !== high ? `${low} to ${high}` : 'more or less'}.`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return [...notes];
|
|
383
|
+
}
|
|
384
|
+
export function toWorkflowDetail(row, options = {}) {
|
|
385
|
+
const contents = parseJsonValue(row.contents);
|
|
386
|
+
const contentsObject = isPlainObject(contents) ? contents : {};
|
|
387
|
+
// A row with rejectreason comes from the caller's own workspace; only then
|
|
388
|
+
// are its state and rejectReason kept.
|
|
389
|
+
const insider = Object.prototype.hasOwnProperty.call(row, 'rejectreason');
|
|
390
|
+
const isProtected = toInteger(row.isprotected) === 1;
|
|
391
|
+
const isClone = row.clonedfromid !== undefined && row.clonedfromid !== null && row.clonedfromid !== '';
|
|
392
|
+
const graphHidden = row.estimatedcost !== undefined
|
|
393
|
+
|| (isProtected && workflowStepCount(contentsObject) === 0);
|
|
394
|
+
const estimated = isPlainObject(row.estimatedcost) ? row.estimatedcost : undefined;
|
|
395
|
+
const notes = estimateNotes(estimated?.details);
|
|
396
|
+
const description = asString(row.description);
|
|
397
|
+
return {
|
|
398
|
+
...toWorkflowSummary(row, { includeState: insider }),
|
|
399
|
+
// The full text: an edit starts from this value, so it must not be shortened.
|
|
400
|
+
...(description ? { description } : {}),
|
|
401
|
+
// A hidden graph arrives emptied, so its own count would read 0; the row's
|
|
402
|
+
// stepcount, when sent, is kept instead.
|
|
403
|
+
...(graphHidden ? {} : { stepCount: workflowStepCount(contentsObject) }),
|
|
404
|
+
insider,
|
|
405
|
+
canEditGraph: insider && !graphHidden && !(isClone && isProtected),
|
|
406
|
+
graphHidden,
|
|
407
|
+
...(row.ownerinactive === true ? { ownerInactive: true } : {}),
|
|
408
|
+
inputs: workflowInputs(contentsObject),
|
|
409
|
+
outputs: workflowOutputs(contentsObject),
|
|
410
|
+
...(estimated
|
|
411
|
+
? {
|
|
412
|
+
estimatedCost: {
|
|
413
|
+
...(asString(estimated.type) ? { type: asString(estimated.type) } : {}),
|
|
414
|
+
valueUsd: toNumber(estimated.value),
|
|
415
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
: {}),
|
|
419
|
+
...(options.includeGraph !== false && !graphHidden && isPlainObject(contents)
|
|
420
|
+
? { contents: insider ? hideStartValues(contentsObject) : redactNodeInputs(contentsObject) }
|
|
421
|
+
: {}),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/** A value for a one-line summary: long text is cut inside its quotes. */
|
|
425
|
+
function summaryValue(value) {
|
|
426
|
+
if (typeof value === 'string')
|
|
427
|
+
return JSON.stringify(truncate(value, 80));
|
|
428
|
+
return truncate(JSON.stringify(value) ?? '', 80);
|
|
429
|
+
}
|
|
430
|
+
export function formatWorkflowInputs(inputs) {
|
|
431
|
+
if (inputs.length === 0)
|
|
432
|
+
return ['No inputs. Run it with no `input`.'];
|
|
433
|
+
return inputs.map(input => {
|
|
434
|
+
const kind = input.field ? `${input.type}, ${input.field}` : input.type;
|
|
435
|
+
const fallback = input.savedValue !== undefined
|
|
436
|
+
? ` (saved value: ${summaryValue(input.savedValue)})`
|
|
437
|
+
: input.default !== undefined && input.default !== ''
|
|
438
|
+
? ` (default: ${summaryValue(input.default)})`
|
|
439
|
+
: '';
|
|
440
|
+
const options = input.options ? ` options: ${input.options.map(option => option.value).join(', ')}` : '';
|
|
441
|
+
return `- \`${input.key}\` (${kind}): ${input.label}${fallback}${options}`;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
// Runs
|
|
446
|
+
export const runOutputItemSchema = z.object({
|
|
447
|
+
url: z.string().optional(),
|
|
448
|
+
name: z.string().optional(),
|
|
449
|
+
mimeType: z.string().optional(),
|
|
450
|
+
sizeBytes: z.number().nullable().optional(),
|
|
451
|
+
text: z.string().optional(),
|
|
452
|
+
});
|
|
453
|
+
export const runStepSchema = z.object({
|
|
454
|
+
nodeId: z.string(),
|
|
455
|
+
tool: z.string().optional(),
|
|
456
|
+
kind: z.string().optional(),
|
|
457
|
+
status: z.string(),
|
|
458
|
+
cause: z.string().optional(),
|
|
459
|
+
error: z.string().optional(),
|
|
460
|
+
});
|
|
461
|
+
export const workflowRunSchema = z.object({
|
|
462
|
+
workflowtoken: z.string(),
|
|
463
|
+
workflowSlug: z.string().optional(),
|
|
464
|
+
status: z.string(),
|
|
465
|
+
outcome: z.string().optional(),
|
|
466
|
+
canResume: z.boolean(),
|
|
467
|
+
graphMasked: z.boolean().optional(),
|
|
468
|
+
breakpointNodeId: z.string().nullable().optional(),
|
|
469
|
+
startedAt: z.string().optional(),
|
|
470
|
+
resumedAt: z.string().optional(),
|
|
471
|
+
endedAt: z.string().optional(),
|
|
472
|
+
elapsedSeconds: z.number().optional(),
|
|
473
|
+
listPriceUsd: z.number().nullable().optional(),
|
|
474
|
+
chargedUsd: z.number().nullable().optional(),
|
|
475
|
+
discountUsd: z.number().nullable().optional(),
|
|
476
|
+
outputs: z.record(z.string(), z.array(runOutputItemSchema)),
|
|
477
|
+
unresolvedOutputs: z.array(z.object({ field: z.string(), reason: z.string() })).optional(),
|
|
478
|
+
steps: z.array(runStepSchema),
|
|
479
|
+
counts: z.object({
|
|
480
|
+
failed: z.number(),
|
|
481
|
+
aborted: z.number(),
|
|
482
|
+
cancelled: z.number(),
|
|
483
|
+
pending: z.number(),
|
|
484
|
+
running: z.number(),
|
|
485
|
+
completed: z.number(),
|
|
486
|
+
}),
|
|
487
|
+
});
|
|
488
|
+
export function isRunTerminal(status) {
|
|
489
|
+
return RUN_TERMINAL_STATUSES.includes(String(status));
|
|
490
|
+
}
|
|
491
|
+
function msToIso(value) {
|
|
492
|
+
const ms = toNumber(value);
|
|
493
|
+
return ms !== null && ms > 0 ? new Date(ms).toISOString() : undefined;
|
|
494
|
+
}
|
|
495
|
+
export function isStrictlyEmpty(value) {
|
|
496
|
+
if (value === null || value === undefined || value === '')
|
|
497
|
+
return true;
|
|
498
|
+
if (Array.isArray(value))
|
|
499
|
+
return value.length === 0;
|
|
500
|
+
if (isPlainObject(value))
|
|
501
|
+
return Object.keys(value).length === 0;
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
function normalizeOutputValue(value, out, depth = 0) {
|
|
505
|
+
if (depth > 6 || value === null || value === undefined)
|
|
506
|
+
return;
|
|
507
|
+
if (Array.isArray(value)) {
|
|
508
|
+
for (const entry of value)
|
|
509
|
+
normalizeOutputValue(entry, out, depth + 1);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (typeof value === 'string') {
|
|
513
|
+
if (/^https?:\/\//i.test(value.trim()))
|
|
514
|
+
out.push({ url: value.trim() });
|
|
515
|
+
else if (value)
|
|
516
|
+
out.push({ text: truncate(value, 20_000) });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
520
|
+
out.push({ text: String(value) });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (!isPlainObject(value))
|
|
524
|
+
return;
|
|
525
|
+
if (Array.isArray(value.children) && value.children.length > 0) {
|
|
526
|
+
normalizeOutputValue(value.children, out, depth + 1);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (value.contenttype === 'raw' && isPlainObject(value.content)) {
|
|
530
|
+
const answer = value.content.answer;
|
|
531
|
+
const text = Array.isArray(answer)
|
|
532
|
+
? answer.map(part => (typeof part === 'string' ? part : isPlainObject(part) ? asString(part.text) ?? '' : '')).join('')
|
|
533
|
+
: asString(answer) ?? '';
|
|
534
|
+
if (text)
|
|
535
|
+
out.push({ text: truncate(text, 20_000) });
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (typeof value.url === 'string' && value.url) {
|
|
539
|
+
out.push({
|
|
540
|
+
url: value.url,
|
|
541
|
+
...(asString(value.name) ? { name: asString(value.name) } : {}),
|
|
542
|
+
...(asString(value.contenttype) ? { mimeType: asString(value.contenttype) } : {}),
|
|
543
|
+
sizeBytes: toNumber(value.size),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Resolves the End node's fields against the run's step outputs, following the
|
|
549
|
+
* run engine: '$node-X.outputs' is a model step's output list, '$node-X.<port>'
|
|
550
|
+
* a utility port, '$input.<key>' the run's Start value, anything else a literal.
|
|
551
|
+
*/
|
|
552
|
+
export function resolveRunOutputs(contents, nodes) {
|
|
553
|
+
const outputs = {};
|
|
554
|
+
const unresolved = [];
|
|
555
|
+
const fields = workflowOutputs(contents);
|
|
556
|
+
const byId = new Map(nodes.map(node => [asString(node.nodeId) ?? '', node]));
|
|
557
|
+
const contentsObject = isPlainObject(contents) ? contents : {};
|
|
558
|
+
const schema = isPlainObject(contentsObject.schema) ? contentsObject.schema : {};
|
|
559
|
+
const schemaInput = isPlainObject(schema.input) ? schema.input : {};
|
|
560
|
+
const metadata = isPlainObject(contentsObject.metadata) ? contentsObject.metadata : {};
|
|
561
|
+
const metadataInput = isPlainObject(metadata.input) ? metadata.input : {};
|
|
562
|
+
const values = isPlainObject(metadataInput.values) ? metadataInput.values : {};
|
|
563
|
+
for (const [field, reference] of Object.entries(fields)) {
|
|
564
|
+
const references = Array.isArray(reference) ? reference : [reference];
|
|
565
|
+
const items = [];
|
|
566
|
+
for (const ref of references) {
|
|
567
|
+
if (typeof ref !== 'string' || !ref.startsWith('$')) {
|
|
568
|
+
normalizeOutputValue(ref, items);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (ref.startsWith('$input.')) {
|
|
572
|
+
const key = ref.slice('$input.'.length);
|
|
573
|
+
const stored = Object.prototype.hasOwnProperty.call(values, key) ? values[key] : undefined;
|
|
574
|
+
const fieldDef = isPlainObject(schemaInput[key]) ? schemaInput[key] : {};
|
|
575
|
+
normalizeOutputValue(isStrictlyEmpty(stored) ? fieldDef.defaultValue : stored, items);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
const match = /^\$(node-[^.]+)\.(.+)$/.exec(ref);
|
|
579
|
+
if (!match) {
|
|
580
|
+
unresolved.push({ field, reason: `Malformed reference ${ref}` });
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
const node = byId.get(match[1]);
|
|
584
|
+
if (!node) {
|
|
585
|
+
unresolved.push({ field, reason: `Step ${match[1]} is not part of this run's results` });
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (node.status !== 'completed') {
|
|
589
|
+
unresolved.push({ field, reason: `Step ${match[1]} is ${String(node.status)}` });
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const stepOutputs = node.outputs;
|
|
593
|
+
if (match[2] === 'outputs' && Array.isArray(stepOutputs)) {
|
|
594
|
+
normalizeOutputValue(stepOutputs, items);
|
|
595
|
+
}
|
|
596
|
+
else if (isPlainObject(stepOutputs) && Object.prototype.hasOwnProperty.call(stepOutputs, match[2])) {
|
|
597
|
+
normalizeOutputValue(stepOutputs[match[2]], items);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
unresolved.push({ field, reason: `Step ${match[1]} has no output "${match[2]}"` });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
outputs[field] = items;
|
|
604
|
+
}
|
|
605
|
+
return { outputs, unresolved };
|
|
606
|
+
}
|
|
607
|
+
export function toWorkflowRun(run, nodes) {
|
|
608
|
+
const status = asString(run.status) ?? 'running';
|
|
609
|
+
const terminal = isRunTerminal(status);
|
|
610
|
+
const resolved = terminal ? resolveRunOutputs(run.contents, nodes) : { outputs: {}, unresolved: [] };
|
|
611
|
+
const counts = { failed: 0, aborted: 0, cancelled: 0, pending: 0, running: 0, completed: 0 };
|
|
612
|
+
const steps = [];
|
|
613
|
+
for (const node of nodes) {
|
|
614
|
+
const nodeStatus = asString(node.status) ?? 'pending';
|
|
615
|
+
if (nodeStatus in counts)
|
|
616
|
+
counts[nodeStatus] += 1;
|
|
617
|
+
if (nodeStatus === 'completed')
|
|
618
|
+
continue;
|
|
619
|
+
const error = asString(node.error);
|
|
620
|
+
steps.push({
|
|
621
|
+
nodeId: asString(node.nodeId) ?? '',
|
|
622
|
+
...(asString(node.tool) ? { tool: asString(node.tool) } : {}),
|
|
623
|
+
...(asString(node.kind) ? { kind: asString(node.kind) } : {}),
|
|
624
|
+
status: nodeStatus,
|
|
625
|
+
...(asString(node.cause) ? { cause: asString(node.cause) } : {}),
|
|
626
|
+
...(error ? { error: truncate(error, 500) } : {}),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
workflowtoken: asString(run.workflowtoken) ?? '',
|
|
631
|
+
...(asString(run.workflowslug) ? { workflowSlug: asString(run.workflowslug) } : {}),
|
|
632
|
+
status,
|
|
633
|
+
...(asString(run.outcome) ? { outcome: asString(run.outcome) } : {}),
|
|
634
|
+
canResume: run.canresume === true,
|
|
635
|
+
...(run.graphmasked === true ? { graphMasked: true } : {}),
|
|
636
|
+
...(run.breakpointnodeid !== undefined ? { breakpointNodeId: asString(run.breakpointnodeid) ?? null } : {}),
|
|
637
|
+
...(msToIso(run.startedAt) ? { startedAt: msToIso(run.startedAt) } : {}),
|
|
638
|
+
...(msToIso(run.resumedAt) ? { resumedAt: msToIso(run.resumedAt) } : {}),
|
|
639
|
+
...(msToIso(run.endedAt) ? { endedAt: msToIso(run.endedAt) } : {}),
|
|
640
|
+
...(toNumber(run.elapsedseconds) !== null ? { elapsedSeconds: toNumber(run.elapsedseconds) } : {}),
|
|
641
|
+
...(run.totalcost !== undefined ? { listPriceUsd: toNumber(run.totalcost) } : {}),
|
|
642
|
+
...(run.chargedcost !== undefined ? { chargedUsd: toNumber(run.chargedcost) } : {}),
|
|
643
|
+
...(run.discountamount !== undefined ? { discountUsd: toNumber(run.discountamount) } : {}),
|
|
644
|
+
outputs: resolved.outputs,
|
|
645
|
+
...(resolved.unresolved.length > 0 ? { unresolvedOutputs: resolved.unresolved } : {}),
|
|
646
|
+
steps,
|
|
647
|
+
counts,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
export function runMediaItems(run) {
|
|
651
|
+
const items = [];
|
|
652
|
+
for (const [field, values] of Object.entries(run.outputs)) {
|
|
653
|
+
for (const value of values) {
|
|
654
|
+
if (value.url)
|
|
655
|
+
items.push({ url: value.url, name: value.name || field, ...(value.mimeType ? { mimeType: value.mimeType } : {}) });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return items;
|
|
659
|
+
}
|
|
660
|
+
export function formatWorkflowRun(run) {
|
|
661
|
+
const lines = ['## Workflow Run', ''];
|
|
662
|
+
lines.push(`**Status:** ${run.status}`);
|
|
663
|
+
lines.push(`**Run token:** \`${run.workflowtoken}\``);
|
|
664
|
+
if (run.workflowSlug)
|
|
665
|
+
lines.push(`**Workflow:** ${workflowUrl(run.workflowSlug)}`);
|
|
666
|
+
if (run.elapsedSeconds !== undefined)
|
|
667
|
+
lines.push(`**Duration:** ${run.elapsedSeconds}s`);
|
|
668
|
+
if (run.chargedUsd !== undefined && run.chargedUsd !== null) {
|
|
669
|
+
lines.push(`**Charged:** $${run.chargedUsd}`);
|
|
670
|
+
}
|
|
671
|
+
else if (run.listPriceUsd !== undefined && run.listPriceUsd !== null) {
|
|
672
|
+
lines.push(`**Cost so far (list price):** $${run.listPriceUsd}`);
|
|
673
|
+
}
|
|
674
|
+
const unavailable = new Set((run.unresolvedOutputs ?? []).map(missing => missing.field));
|
|
675
|
+
const fields = Object.entries(run.outputs);
|
|
676
|
+
if (fields.length > 0) {
|
|
677
|
+
lines.push('', '### Outputs', '');
|
|
678
|
+
for (const [field, values] of fields) {
|
|
679
|
+
if (values.length === 0) {
|
|
680
|
+
// A field fed by a step that did not finish is listed once, below, as not available.
|
|
681
|
+
if (!unavailable.has(field))
|
|
682
|
+
lines.push(`- **${field}:** (empty)`);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
for (const value of values) {
|
|
686
|
+
if (value.url)
|
|
687
|
+
lines.push(`- **${field}:** ${value.url}`);
|
|
688
|
+
else if (value.text)
|
|
689
|
+
lines.push(`- **${field}:** ${truncate(value.text, 2_000)}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
for (const missing of run.unresolvedOutputs ?? []) {
|
|
694
|
+
lines.push(`- **${missing.field}:** not available (${missing.reason})`);
|
|
695
|
+
}
|
|
696
|
+
const problems = run.steps.filter(step => step.status === 'failed' || step.status === 'aborted' || step.status === 'cancelled');
|
|
697
|
+
if (problems.length > 0) {
|
|
698
|
+
lines.push('', '### Steps that did not finish', '');
|
|
699
|
+
for (const step of problems) {
|
|
700
|
+
// A cause that only repeats the status, as a cancelled step's does, is left out.
|
|
701
|
+
const reason = step.status === 'aborted'
|
|
702
|
+
? `skipped because an earlier step ${step.cause === 'earlier-cancellation-in-run' ? 'was cancelled' : 'failed'}`
|
|
703
|
+
: step.error || (step.cause && step.cause !== step.status ? step.cause : '');
|
|
704
|
+
lines.push(`- \`${step.nodeId}\`${step.tool ? ` (${step.tool})` : ''}: ${step.status}${reason ? `, ${reason}` : ''}`);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (run.canResume) {
|
|
708
|
+
lines.push('', 'This run can be resumed with `resume_workflow_run`. Completed steps are kept and not charged again.');
|
|
709
|
+
}
|
|
710
|
+
return lines.join('\n');
|
|
711
|
+
}
|
|
712
|
+
//# sourceMappingURL=workflow.js.map
|