@wiro-ai/wiro-mcp 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -16
- package/dist/client.d.ts +10 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +39 -20
- 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 +25 -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 +665 -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 +9 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -3
- 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 +545 -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 +274 -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 +103 -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 +144 -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 +455 -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 +110 -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 +224 -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 +107 -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 +136 -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 +78 -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.map +1 -1
- package/dist/tools/get-task-price.js +42 -15
- 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 +4 -3
- 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 +99 -0
- package/dist/tools/get-workflow.js.map +1 -0
- package/dist/tools/index.d.ts +62 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +127 -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 +90 -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 +764 -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 +537 -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 +39 -3
- 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 +283 -0
- package/dist/tools/save-workflow-from-agent.js.map +1 -0
- package/dist/tools/schemas.d.ts +62 -12
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +17 -3
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/search-models.js +6 -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 +529 -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 +178 -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 +4 -3
- package/dist/tools/upload-file.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 +12 -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 +388 -0
- package/dist/utils/agent-catalog.js.map +1 -0
- package/dist/utils/agent-config.d.ts +441 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +1667 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/agent-message.d.ts +115 -0
- package/dist/utils/agent-message.d.ts.map +1 -0
- package/dist/utils/agent-message.js +246 -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 +408 -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 +623 -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 +38 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +162 -13
- package/dist/utils/format.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 +2 -0
- package/dist/utils/structured.d.ts.map +1 -1
- package/dist/utils/structured.js +9 -6
- 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 +1140 -0
- package/dist/utils/user-agent.d.ts.map +1 -0
- package/dist/utils/user-agent.js +802 -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 +123 -0
- package/dist/utils/workflow-save.d.ts.map +1 -0
- package/dist/utils/workflow-save.js +734 -0
- package/dist/utils/workflow-save.js.map +1 -0
- package/dist/utils/workflow.d.ts +559 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +613 -0
- package/dist/utils/workflow.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { WiroApiError } from '../client.js';
|
|
3
|
+
import { nextActionSchema } from '../tools/schemas.js';
|
|
4
|
+
import { ApiResultError, asString, isPlainObject, toNumber, truncate, unixToIso, } from './api.js';
|
|
5
|
+
import { agentMessageSchema, extractMediaUrls, extractReply, formatAgentMessage, isEarlyNotFound, isTerminalMessageStatus, messageState, toAgentMessage, } from './agent-message.js';
|
|
6
|
+
import { BARE_UNAVAILABLE_PATTERN, agentRequestBody, callAgent, isBackgroundHandOff, readAgentMessage, } from './agent-targets.js';
|
|
7
|
+
import { createMediaContent, guessMimeType, isWiroHostedUrl } from './media.js';
|
|
8
|
+
import { defaultInterval, pollUntil, reportProgress } from './poll.js';
|
|
9
|
+
import { STUDIO_RUNS_MAX, formatStudioRun, readStudioRunPage, studioRunSchema, } from './studio-runs.js';
|
|
10
|
+
import { attachedWorkflowSchema, listWorkflowsForAgent, summarizeWorkflowReply, workflowArtifactSummarySchema, workflowListResultSchema, workflowModelsText, workflowReplyProse, } from './workflow-agent.js';
|
|
11
|
+
// One agent turn as send_agent_message and get_agent_message report it: wait
|
|
12
|
+
// for the message within the caller's budget, then add what only some agents
|
|
13
|
+
// have (Studio's model runs, the Workflow Agent's artifacts) and the next step.
|
|
14
|
+
const MAX_REPLY_LENGTH = 20_000;
|
|
15
|
+
const MAX_PARTIAL_LENGTH = 2_000;
|
|
16
|
+
// Runs are stamped by the same server clock as the message; the slack only
|
|
17
|
+
// absorbs rounding to whole seconds.
|
|
18
|
+
const RUN_WINDOW_BEFORE_SECONDS = 5;
|
|
19
|
+
const RUN_WINDOW_AFTER_SECONDS = 30;
|
|
20
|
+
// Work handed to the background starts its runs after the turn has ended, and
|
|
21
|
+
// runs are matched to a turn by time. A hand-off's runs are therefore counted
|
|
22
|
+
// until the next message in its session, and for at most this long. Until then get_agent_message keeps pointing to itself while no
|
|
23
|
+
// run has appeared; a text-only result cannot keep an assistant polling forever.
|
|
24
|
+
const BACKGROUND_FOLLOW_UP_SECONDS = 30 * 60;
|
|
25
|
+
// One short page of the session's history finds its next message.
|
|
26
|
+
const NEXT_MESSAGE_HISTORY_LIMIT = 20;
|
|
27
|
+
// A small page of Studio's recent runs covers most turns, so the largest page
|
|
28
|
+
// is read only when the small one does not reach back to the start of the turn.
|
|
29
|
+
const STUDIO_RUNS_PAGE = 30;
|
|
30
|
+
// The first run of a hand-off is looked for less often: the reply has already
|
|
31
|
+
// said that its results come later.
|
|
32
|
+
const BACKGROUND_RUN_POLL_MS = 15_000;
|
|
33
|
+
// A read that has not found the message yet is repeated past the 10 seconds a
|
|
34
|
+
// new message can take to become visible.
|
|
35
|
+
const NOT_VISIBLE_RETRY_WAIT_SECONDS = 15;
|
|
36
|
+
// Not a status of Wiro's: the message cannot be read yet.
|
|
37
|
+
const NOT_VISIBLE_STATUS = 'not_visible';
|
|
38
|
+
const STUDIO_RUNS_NOTE = 'These model runs were started by Studio and belong to it: get_task, cancel_task and kill_task '
|
|
39
|
+
+ 'cannot read or stop them. Use get_agent_message or list_studio_results to follow them.';
|
|
40
|
+
export const agentTurnOutputSchema = {
|
|
41
|
+
agent: z.string(),
|
|
42
|
+
agentKind: z.enum(['studio', 'workflow-agent', 'deployed']),
|
|
43
|
+
session: z.string(),
|
|
44
|
+
sessionCreated: z.boolean().optional(),
|
|
45
|
+
done: z.boolean(),
|
|
46
|
+
message: agentMessageSchema,
|
|
47
|
+
partialReply: z.string().optional(),
|
|
48
|
+
background: z.boolean().optional(),
|
|
49
|
+
modelChange: z.object({
|
|
50
|
+
fromModel: z.string().optional(),
|
|
51
|
+
toModel: z.string().optional(),
|
|
52
|
+
}).optional(),
|
|
53
|
+
llmCostUsd: z.number().nullable().optional(),
|
|
54
|
+
modelRuns: z.array(studioRunSchema).optional(),
|
|
55
|
+
modelRunsPending: z.boolean().optional(),
|
|
56
|
+
attachedWorkflow: attachedWorkflowSchema.optional(),
|
|
57
|
+
workflows: z.array(workflowArtifactSummarySchema).optional(),
|
|
58
|
+
workflowList: workflowListResultSchema.optional(),
|
|
59
|
+
notes: z.array(z.string()),
|
|
60
|
+
nextAction: nextActionSchema.optional(),
|
|
61
|
+
};
|
|
62
|
+
export function notFoundMessage(target) {
|
|
63
|
+
if (target.kind === 'deployed') {
|
|
64
|
+
return 'Message not found. Check message_id and agent; a message is visible only to the API key owner who sent it.';
|
|
65
|
+
}
|
|
66
|
+
return 'Message not found. Pass the same session the message was sent in (send_agent_message returns it); '
|
|
67
|
+
+ 'without one, the session "default" is searched.'
|
|
68
|
+
+ (target.kind === 'workflow-agent'
|
|
69
|
+
? ' A Workflow Agent message is visible only to API keys of the project that sent it: one sent with another'
|
|
70
|
+
+ ' project\'s key, or on wiro.ai, cannot be read here.'
|
|
71
|
+
: '');
|
|
72
|
+
}
|
|
73
|
+
function isMessageNotFound(error) {
|
|
74
|
+
return error instanceof ApiResultError && /message not found/i.test(error.message);
|
|
75
|
+
}
|
|
76
|
+
/** A short reason for a failed read, without response bodies. */
|
|
77
|
+
function readFailureReason(error) {
|
|
78
|
+
if (isMessageNotFound(error))
|
|
79
|
+
return 'Wiro does not show it yet';
|
|
80
|
+
if (error instanceof WiroApiError)
|
|
81
|
+
return `HTTP ${error.status}`;
|
|
82
|
+
const text = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, ' ').trim();
|
|
83
|
+
// The server's own "please try again" would contradict "do not send it again" next to it.
|
|
84
|
+
if (BARE_UNAVAILABLE_PATTERN.test(text))
|
|
85
|
+
return 'Wiro answered that it is temporarily unavailable';
|
|
86
|
+
return truncate(text.replace(/\s*Please try again\b[^.]*\.?/gi, '').trim() || 'Wiro gave no reason', 200);
|
|
87
|
+
}
|
|
88
|
+
function pendingMessage(messageId, status, state = messageState(status)) {
|
|
89
|
+
return {
|
|
90
|
+
messageId,
|
|
91
|
+
state,
|
|
92
|
+
status,
|
|
93
|
+
mediaUrls: [],
|
|
94
|
+
toolsUsed: [],
|
|
95
|
+
attachments: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** The public message; for the Workflow Agent the reply keeps only the prose. */
|
|
99
|
+
export function projectAgentMessage(target, row, includeUserText) {
|
|
100
|
+
const message = toAgentMessage(row, { includeUserText });
|
|
101
|
+
// A voice call keeps only its id, status and times; its text names the caller.
|
|
102
|
+
if (message.kind === 'voice_call')
|
|
103
|
+
return { message };
|
|
104
|
+
const terminal = isTerminalMessageStatus(row.status);
|
|
105
|
+
if (!terminal) {
|
|
106
|
+
const full = extractReply(row);
|
|
107
|
+
const partial = target.kind === 'workflow-agent' ? workflowReplyProse(full) : full;
|
|
108
|
+
return partial ? { message, partialReply: truncate(partial, MAX_PARTIAL_LENGTH) } : { message };
|
|
109
|
+
}
|
|
110
|
+
if (target.kind !== 'workflow-agent' || row.status !== 'agent_end')
|
|
111
|
+
return { message };
|
|
112
|
+
// The fenced JSON can be 40 KB; the reply keeps only the prose around it.
|
|
113
|
+
const workflowReply = summarizeWorkflowReply(extractReply(row));
|
|
114
|
+
const reply = truncate(workflowReply.prose, MAX_REPLY_LENGTH);
|
|
115
|
+
const { reply: _reply, replyTruncated: _truncated, ...rest } = message;
|
|
116
|
+
return {
|
|
117
|
+
message: {
|
|
118
|
+
...rest,
|
|
119
|
+
...(reply ? { reply } : {}),
|
|
120
|
+
...(reply.length < workflowReply.prose.length ? { replyTruncated: true } : {}),
|
|
121
|
+
mediaUrls: extractMediaUrls(workflowReply.prose),
|
|
122
|
+
},
|
|
123
|
+
workflowReply,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function turnCreatedAt(options, row) {
|
|
127
|
+
return toNumber(row.createdat) ?? options.sentAt ?? Math.floor(Date.now() / 1000);
|
|
128
|
+
}
|
|
129
|
+
/** When the next user message in the turn's session was created, from one short history page. */
|
|
130
|
+
async function nextUserMessageAt(options, createdAt) {
|
|
131
|
+
const { client, target, session, messageId, extra } = options;
|
|
132
|
+
if (session === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
try {
|
|
135
|
+
const response = await callAgent(client, target, 'history', agentRequestBody(target, session, { limit: NEXT_MESSAGE_HISTORY_LIMIT }), extra?.signal);
|
|
136
|
+
const data = response.result === true && isPlainObject(response.data) ? response.data : {};
|
|
137
|
+
const rows = Array.isArray(data.messages) ? data.messages.filter(isPlainObject) : [];
|
|
138
|
+
// Results, notices and model switches have no user text of their own.
|
|
139
|
+
const later = rows
|
|
140
|
+
.filter(item => asString(item.guid) !== messageId && asString(item.content)?.trim())
|
|
141
|
+
.map(item => toNumber(item.createdat))
|
|
142
|
+
.filter((at) => at !== null && at > createdAt);
|
|
143
|
+
return later.length > 0 ? Math.min(...later) : undefined;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (extra?.signal.aborted)
|
|
147
|
+
throw error;
|
|
148
|
+
// The fixed limit still bounds the window.
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function collectStudioRuns(options, row, terminal, background, deadline, notes) {
|
|
153
|
+
const createdAt = turnCreatedAt(options, row);
|
|
154
|
+
const endedAt = toNumber(row.endedat);
|
|
155
|
+
// A turn's runs are found by the time they were created.
|
|
156
|
+
const window = { from: createdAt - RUN_WINDOW_BEFORE_SECONDS };
|
|
157
|
+
let endsAtNextMessage = false;
|
|
158
|
+
if (background) {
|
|
159
|
+
const handOff = endedAt ?? createdAt;
|
|
160
|
+
const limit = handOff + BACKGROUND_FOLLOW_UP_SECONDS;
|
|
161
|
+
const nextMessage = await nextUserMessageAt(options, createdAt);
|
|
162
|
+
endsAtNextMessage = nextMessage !== undefined && nextMessage < limit;
|
|
163
|
+
window.to = Math.max(handOff + RUN_WINDOW_AFTER_SECONDS, Math.min(nextMessage ?? limit, limit));
|
|
164
|
+
}
|
|
165
|
+
else if (terminal && endedAt !== null) {
|
|
166
|
+
window.to = endedAt + RUN_WINDOW_AFTER_SECONDS;
|
|
167
|
+
}
|
|
168
|
+
const windowClosed = () => window.to !== undefined && Date.now() / 1000 > window.to;
|
|
169
|
+
let pageSize = STUDIO_RUNS_PAGE;
|
|
170
|
+
let mayMissOlder = false;
|
|
171
|
+
let found = 0;
|
|
172
|
+
try {
|
|
173
|
+
const poll = await pollUntil({
|
|
174
|
+
read: async (signal) => {
|
|
175
|
+
let page = await readStudioRunPage(options.client, pageSize, window, signal);
|
|
176
|
+
if (page.mayMissOlder && pageSize < STUDIO_RUNS_MAX) {
|
|
177
|
+
pageSize = STUDIO_RUNS_MAX;
|
|
178
|
+
page = await readStudioRunPage(options.client, pageSize, window, signal);
|
|
179
|
+
}
|
|
180
|
+
mayMissOlder = page.mayMissOlder;
|
|
181
|
+
found = page.runs.length;
|
|
182
|
+
return page.runs;
|
|
183
|
+
},
|
|
184
|
+
// A hand-off is waited on until its first run shows up or its window closes.
|
|
185
|
+
isDone: runs => !terminal
|
|
186
|
+
|| (runs.every(run => run.state !== 'running') && (!background || runs.length > 0 || windowClosed())),
|
|
187
|
+
timeoutMs: terminal ? Math.max(0, deadline - Date.now()) : 0,
|
|
188
|
+
signal: options.extra?.signal,
|
|
189
|
+
interval: elapsedMs => (background && found === 0 ? BACKGROUND_RUN_POLL_MS : defaultInterval(elapsedMs)),
|
|
190
|
+
onPoll: async (runs, elapsedMs) => {
|
|
191
|
+
const running = runs.filter(run => run.state === 'running').length;
|
|
192
|
+
if (running > 0)
|
|
193
|
+
await reportProgress(options.extra, elapsedMs, `Studio: ${running} model run(s) still running`);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
if (mayMissOlder) {
|
|
197
|
+
notes.push(`Studio lists only its ${STUDIO_RUNS_MAX} most recent model runs, and this turn started before the `
|
|
198
|
+
+ 'oldest of them, so some of its runs may be missing here.');
|
|
199
|
+
}
|
|
200
|
+
if (poll.value === undefined) {
|
|
201
|
+
notes.push('The model runs of this turn could not be read just now. Call get_agent_message again to collect '
|
|
202
|
+
+ 'them; list_studio_results lists them too.');
|
|
203
|
+
return { pending: false };
|
|
204
|
+
}
|
|
205
|
+
const runs = [...poll.value].reverse();
|
|
206
|
+
return {
|
|
207
|
+
runs,
|
|
208
|
+
pending: runs.some(run => run.state === 'running'),
|
|
209
|
+
...(background && windowClosed() ? { closedBy: endsAtNextMessage ? 'next message' : 'time' } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (options.extra?.signal.aborted)
|
|
214
|
+
throw error;
|
|
215
|
+
notes.push(`The model runs of this turn could not be read (${readFailureReason(error)}). Call get_agent_message `
|
|
216
|
+
+ 'again to collect them; list_studio_results lists them too.');
|
|
217
|
+
return { pending: false };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function backgroundNote(target) {
|
|
221
|
+
if (target.kind === 'studio') {
|
|
222
|
+
return 'Studio handed this work to the background, so its results come later. get_agent_message collects the '
|
|
223
|
+
+ 'model runs it starts there, and list_studio_results lists them too. A text follow-up can also arrive as a '
|
|
224
|
+
+ 'separate message in this session: get_agent_conversation shows it with the API key of a personal project, '
|
|
225
|
+
+ 'but not with a team project\'s key. Do not send the request again.';
|
|
226
|
+
}
|
|
227
|
+
return 'The agent handed this work to the background. Its result arrives later as a separate message in this '
|
|
228
|
+
+ 'session: read it with get_agent_conversation. Do not send the request again.';
|
|
229
|
+
}
|
|
230
|
+
const FOLLOW_UP_MINUTES = BACKGROUND_FOLLOW_UP_SECONDS / 60;
|
|
231
|
+
const HAND_OFF_RUNS_BY_TIME_NOTE = 'Studio does not record which message started a run, so the runs of a hand-off '
|
|
232
|
+
+ 'are matched by time: runs started after this message and before the next message in this session, up to '
|
|
233
|
+
+ `${FOLLOW_UP_MINUTES} minutes after the hand-off. A run started from another Studio conversation in that time `
|
|
234
|
+
+ 'can show up here too.';
|
|
235
|
+
function handOffClosedNote(closedBy, anyRuns) {
|
|
236
|
+
if (closedBy === 'next message') {
|
|
237
|
+
return anyRuns
|
|
238
|
+
? 'Runs started after the next message in this session are not counted for this turn; list_studio_results '
|
|
239
|
+
+ 'shows them.'
|
|
240
|
+
: 'No model run appeared before the next message in this session, so the background work may have answered '
|
|
241
|
+
+ 'with text only. list_studio_results shows any run it starts later.';
|
|
242
|
+
}
|
|
243
|
+
return anyRuns
|
|
244
|
+
? `Runs started more than ${FOLLOW_UP_MINUTES} minutes after the hand-off are not counted for this turn; `
|
|
245
|
+
+ 'list_studio_results shows them.'
|
|
246
|
+
: `No model run has appeared in the ${FOLLOW_UP_MINUTES} minutes since the hand-off, so the background work may `
|
|
247
|
+
+ 'have answered with text only. list_studio_results shows any run it starts later.';
|
|
248
|
+
}
|
|
249
|
+
function workflowChoiceNote(rows, session) {
|
|
250
|
+
const only = rows.length === 1 ? rows[0] : undefined;
|
|
251
|
+
const send = `send_agent_message with agent "workflow-agent", session "${session}"`;
|
|
252
|
+
return only
|
|
253
|
+
? `The Workflow Agent needs the workflow attached to edit it. Confirm with the user that they mean "${only.title}", `
|
|
254
|
+
+ `then call ${send}, workflow "${only.fullSlug}" and their edit request as message. That is a new, billed turn.`
|
|
255
|
+
: 'The Workflow Agent needs the workflow attached to edit it. Ask the user which of the listed workflows they '
|
|
256
|
+
+ `mean, then call ${send}, workflow set to that workflow's fullSlug and their edit request as message. That is `
|
|
257
|
+
+ 'a new, billed turn.';
|
|
258
|
+
}
|
|
259
|
+
export async function collectTurn(options) {
|
|
260
|
+
const { client, target, messageId } = options;
|
|
261
|
+
const deadline = Date.now() + options.waitSeconds * 1000;
|
|
262
|
+
const notes = [];
|
|
263
|
+
const afterSend = options.sentStatus !== undefined;
|
|
264
|
+
let row;
|
|
265
|
+
let done = false;
|
|
266
|
+
let readFailure = options.readFailure;
|
|
267
|
+
let invisible = false;
|
|
268
|
+
if (!options.skipRead && readFailure === undefined) {
|
|
269
|
+
let lastError;
|
|
270
|
+
try {
|
|
271
|
+
const poll = await pollUntil({
|
|
272
|
+
read: async (signal) => {
|
|
273
|
+
try {
|
|
274
|
+
return await readAgentMessage(client, target, messageId, options.session, signal);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
lastError = error;
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
isDone: value => isTerminalMessageStatus(value.status),
|
|
282
|
+
timeoutMs: options.waitSeconds * 1000,
|
|
283
|
+
signal: options.extra?.signal,
|
|
284
|
+
// A new message can take a moment to become readable. After Send the
|
|
285
|
+
// message is known to exist, so "not found" is read again for the
|
|
286
|
+
// whole wait; otherwise only for the first seconds.
|
|
287
|
+
isRetryable: (error, elapsedMs) => (afterSend ? isMessageNotFound(error) : isEarlyNotFound(error, elapsedMs)),
|
|
288
|
+
onPoll: (value, elapsedMs) => reportProgress(options.extra, elapsedMs, `${target.name}: ${asString(value.status) ?? 'working'}`),
|
|
289
|
+
});
|
|
290
|
+
row = poll.value;
|
|
291
|
+
done = poll.done;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
if (options.extra?.signal.aborted)
|
|
295
|
+
throw error;
|
|
296
|
+
// Once Send has queued the turn, a failed read must not hide its id.
|
|
297
|
+
if (afterSend)
|
|
298
|
+
readFailure = error;
|
|
299
|
+
else if (isMessageNotFound(error))
|
|
300
|
+
throw new ApiResultError(notFoundMessage(target), error.codes, error.body);
|
|
301
|
+
else
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
if (!row && readFailure === undefined && lastError !== undefined) {
|
|
305
|
+
// The wait ended before any read succeeded.
|
|
306
|
+
if (isMessageNotFound(lastError))
|
|
307
|
+
invisible = true;
|
|
308
|
+
else if (afterSend)
|
|
309
|
+
readFailure = lastError;
|
|
310
|
+
else
|
|
311
|
+
throw lastError;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!row && !afterSend && !invisible) {
|
|
315
|
+
throw new ApiResultError(notFoundMessage(target), [], {});
|
|
316
|
+
}
|
|
317
|
+
const session = options.session
|
|
318
|
+
?? (target.kind === 'deployed' && row ? asString(row.sessionkey) : undefined)
|
|
319
|
+
?? 'default';
|
|
320
|
+
const result = {
|
|
321
|
+
agent: target.agent,
|
|
322
|
+
agentKind: target.kind,
|
|
323
|
+
session,
|
|
324
|
+
...(options.sessionCreated ? { sessionCreated: true } : {}),
|
|
325
|
+
done,
|
|
326
|
+
// Until a row is read, only Send's own answer says anything about the message.
|
|
327
|
+
message: afterSend
|
|
328
|
+
? pendingMessage(messageId, options.sentStatus ?? 'agent_queue')
|
|
329
|
+
: pendingMessage(messageId, NOT_VISIBLE_STATUS, 'submitted'),
|
|
330
|
+
notes,
|
|
331
|
+
...(options.modelChange ? { modelChange: options.modelChange } : {}),
|
|
332
|
+
...(options.attachedWorkflow ? { attachedWorkflow: options.attachedWorkflow } : {}),
|
|
333
|
+
};
|
|
334
|
+
const continueArguments = {
|
|
335
|
+
agent: target.agent,
|
|
336
|
+
message_id: messageId,
|
|
337
|
+
...(target.kind !== 'deployed' || options.session !== undefined ? { session } : {}),
|
|
338
|
+
// The attached workflow travels with the wait, so the save step can still apply the proposal to it.
|
|
339
|
+
...(options.attachedWorkflow
|
|
340
|
+
? { workflow: options.attachedWorkflow.fullSlug, loaded_updatetime: options.attachedWorkflow.updatetime }
|
|
341
|
+
: {}),
|
|
342
|
+
};
|
|
343
|
+
const state = {
|
|
344
|
+
invisible,
|
|
345
|
+
afterSendFailure: readFailure !== undefined,
|
|
346
|
+
runsUnread: false,
|
|
347
|
+
awaitingBackgroundRuns: false,
|
|
348
|
+
};
|
|
349
|
+
if (options.sessionCreated) {
|
|
350
|
+
notes.push(`This message started a new session. Pass session "${session}" to continue the same conversation.`);
|
|
351
|
+
}
|
|
352
|
+
if (readFailure !== undefined) {
|
|
353
|
+
notes.push(`Wiro queued this message (id ${messageId}) but it could not be read just now `
|
|
354
|
+
+ `(${readFailureReason(readFailure)}). It may already be running and is billed as usual. Do not send it `
|
|
355
|
+
+ 'again: follow nextAction to keep waiting for it.');
|
|
356
|
+
}
|
|
357
|
+
else if (invisible && afterSend) {
|
|
358
|
+
notes.push(`Wiro queued this message (id ${messageId}) but does not show it yet. Do not send it again: follow `
|
|
359
|
+
+ 'nextAction to keep waiting for it.');
|
|
360
|
+
}
|
|
361
|
+
else if (invisible) {
|
|
362
|
+
notes.push('Wiro does not show this message yet. A message sent moments ago can take a few seconds to appear. '
|
|
363
|
+
+ 'If it still cannot be found after 10 seconds, the message id or the session is wrong'
|
|
364
|
+
+ (target.kind === 'workflow-agent'
|
|
365
|
+
? ', or it was sent with another project\'s API key or on wiro.ai, which this key cannot read.'
|
|
366
|
+
: '.'));
|
|
367
|
+
}
|
|
368
|
+
if (row) {
|
|
369
|
+
const projected = projectAgentMessage(target, row, options.includeUserText ?? false);
|
|
370
|
+
result.message = projected.message;
|
|
371
|
+
if (projected.partialReply)
|
|
372
|
+
result.partialReply = projected.partialReply;
|
|
373
|
+
const voiceCall = result.message.kind === 'voice_call';
|
|
374
|
+
if (voiceCall)
|
|
375
|
+
notes.push('This is a voice call record. Caller details and transcripts are not returned.');
|
|
376
|
+
if (target.kind !== 'deployed') {
|
|
377
|
+
const credits = toNumber(row.tokencost);
|
|
378
|
+
result.llmCostUsd = credits === null ? null : Math.round(credits * 100) / 10_000;
|
|
379
|
+
}
|
|
380
|
+
const background = done
|
|
381
|
+
&& row.status === 'agent_end'
|
|
382
|
+
&& !voiceCall
|
|
383
|
+
&& isBackgroundHandOff(result.message.reply, result.message.toolsUsed);
|
|
384
|
+
if (background) {
|
|
385
|
+
result.background = true;
|
|
386
|
+
notes.push(backgroundNote(target));
|
|
387
|
+
}
|
|
388
|
+
if (target.kind === 'studio') {
|
|
389
|
+
const collected = await collectStudioRuns(options, row, done, background, deadline, notes);
|
|
390
|
+
if (collected.runs) {
|
|
391
|
+
result.modelRuns = collected.runs;
|
|
392
|
+
if (done && collected.pending)
|
|
393
|
+
result.modelRunsPending = true;
|
|
394
|
+
if (collected.runs.length > 0) {
|
|
395
|
+
notes.push(STUDIO_RUNS_NOTE);
|
|
396
|
+
if (background) {
|
|
397
|
+
notes.push(HAND_OFF_RUNS_BY_TIME_NOTE);
|
|
398
|
+
if (collected.closedBy) {
|
|
399
|
+
notes.push(handOffClosedNote(collected.closedBy, true));
|
|
400
|
+
}
|
|
401
|
+
else if (!collected.pending) {
|
|
402
|
+
notes.push('More runs can still start while the background work goes on; get_agent_message and '
|
|
403
|
+
+ 'list_studio_results show them.');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else if (background) {
|
|
408
|
+
state.awaitingBackgroundRuns = collected.closedBy === undefined;
|
|
409
|
+
if (collected.closedBy)
|
|
410
|
+
notes.push(handOffClosedNote(collected.closedBy, false));
|
|
411
|
+
}
|
|
412
|
+
else if (done && row.status === 'agent_end') {
|
|
413
|
+
const since = unixToIso(turnCreatedAt(options, row));
|
|
414
|
+
notes.push('No model runs were found for this turn. If the reply says results will follow later, '
|
|
415
|
+
+ `list_studio_results${since ? ` with since "${since}"` : ''} shows the runs Studio starts after this message.`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (done) {
|
|
419
|
+
state.runsUnread = true;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (projected.workflowReply) {
|
|
423
|
+
const { workflows, listRequest } = projected.workflowReply;
|
|
424
|
+
result.workflows = workflows;
|
|
425
|
+
if (workflows.some(workflow => !workflow.valid)) {
|
|
426
|
+
notes.push('A workflow in this reply is not valid. Send the issues back to the Workflow Agent in the same '
|
|
427
|
+
+ 'session and ask it to fix them.');
|
|
428
|
+
}
|
|
429
|
+
if (listRequest) {
|
|
430
|
+
result.workflowList = await listWorkflowsForAgent(client, listRequest, session, options.extra?.signal);
|
|
431
|
+
const rows = result.workflowList.rows;
|
|
432
|
+
// The edit request is the user's own words and the send is a billed
|
|
433
|
+
// turn, so this is said in a note rather than offered as a call.
|
|
434
|
+
if (rows.length > 0 && !workflows.some(workflow => workflow.valid && workflow.artifactHash)) {
|
|
435
|
+
notes.push(workflowChoiceNote(rows, session));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (done && row.status === 'agent_error') {
|
|
440
|
+
notes.push('The agent could not finish this turn. Nothing was retried; send the message again if the user wants to.');
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
result.nextAction = chooseNextAction(target, result, continueArguments, state, afterSend);
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
function chooseNextAction(target, result, continueArguments, state, afterSend) {
|
|
447
|
+
if (!result.done) {
|
|
448
|
+
if (state.invisible && !afterSend) {
|
|
449
|
+
return {
|
|
450
|
+
tool: 'get_agent_message',
|
|
451
|
+
arguments: { ...continueArguments, wait_seconds: NOT_VISIBLE_RETRY_WAIT_SECONDS },
|
|
452
|
+
reason: 'Wiro does not show this message yet. Call this to wait for it; do not send the message again.'
|
|
453
|
+
+ (target.kind === 'workflow-agent'
|
|
454
|
+
? ' A message sent with another project\'s API key or on wiro.ai cannot be read with this key.'
|
|
455
|
+
: ''),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
tool: 'get_agent_message',
|
|
460
|
+
arguments: continueArguments,
|
|
461
|
+
reason: state.afterSendFailure || state.invisible
|
|
462
|
+
? `${target.name} has this message queued. Call this to keep waiting for it; do not send the message again.`
|
|
463
|
+
: `${target.name} is still working. Call this to keep waiting; do not send the message again.`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
if (result.modelRunsPending) {
|
|
467
|
+
return {
|
|
468
|
+
tool: 'get_agent_message',
|
|
469
|
+
arguments: continueArguments,
|
|
470
|
+
reason: 'Model runs started by this turn are still running. Call this to collect their outputs.',
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const savable = result.workflows?.find(workflow => workflow.valid && workflow.artifactHash);
|
|
474
|
+
if (savable) {
|
|
475
|
+
const attached = result.attachedWorkflow;
|
|
476
|
+
return {
|
|
477
|
+
tool: 'save_workflow_from_agent',
|
|
478
|
+
arguments: {
|
|
479
|
+
session: result.session,
|
|
480
|
+
message_id: result.message.messageId,
|
|
481
|
+
artifact_hash: savable.artifactHash,
|
|
482
|
+
...(attached ? { workflow: attached.fullSlug, loaded_updatetime: attached.updatetime } : {}),
|
|
483
|
+
},
|
|
484
|
+
reason: attached
|
|
485
|
+
? `This applies the proposal to "${attached.title ?? attached.fullSlug}". The call first returns a summary of `
|
|
486
|
+
+ 'the change and changes the workflow only when repeated with confirm: true, after the user agrees. Leave '
|
|
487
|
+
+ 'workflow and loaded_updatetime out to save the proposal as a new workflow instead.'
|
|
488
|
+
: 'Show the user what this workflow does and save it only after they agree; it is saved as a new workflow. '
|
|
489
|
+
+ 'If a workflow was attached when this message was sent, pass workflow and loaded_updatetime from that '
|
|
490
|
+
+ 'send\'s attachedWorkflow to apply the change to it instead.',
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (state.runsUnread) {
|
|
494
|
+
return {
|
|
495
|
+
tool: 'get_agent_message',
|
|
496
|
+
arguments: continueArguments,
|
|
497
|
+
reason: 'The model runs of this turn could not be read just now. Call this to read them again; do not send the '
|
|
498
|
+
+ 'message again.',
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
if (result.background) {
|
|
502
|
+
if (target.kind === 'studio') {
|
|
503
|
+
if (!state.awaitingBackgroundRuns)
|
|
504
|
+
return undefined;
|
|
505
|
+
return {
|
|
506
|
+
tool: 'get_agent_message',
|
|
507
|
+
arguments: continueArguments,
|
|
508
|
+
reason: 'Studio is working on this in the background and has not started a model run yet. Call this to wait '
|
|
509
|
+
+ 'for the runs it starts; list_studio_results lists them too. Do not send the request again.',
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
tool: 'get_agent_conversation',
|
|
514
|
+
arguments: { agent: target.agent, session: result.session, limit: 5 },
|
|
515
|
+
reason: 'The result arrives as a later message in this session.',
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
// ─── Text ───────────────────────────────────────────────────────────
|
|
521
|
+
/**
|
|
522
|
+
* Files to present once the turn is over: every model run output, and the
|
|
523
|
+
* reply's own URLs when they point at a media file rather than a web page.
|
|
524
|
+
* Only files on Wiro's own hosts count; anything else stays a plain link.
|
|
525
|
+
*/
|
|
526
|
+
export function turnMediaItems(result) {
|
|
527
|
+
if (!result.done)
|
|
528
|
+
return [];
|
|
529
|
+
// Run outputs first: they carry a name and type, and duplicates keep the first entry.
|
|
530
|
+
const items = (result.modelRuns ?? []).flatMap(run => run.outputs.map(output => ({
|
|
531
|
+
url: output.url,
|
|
532
|
+
...(output.name ? { name: output.name } : {}),
|
|
533
|
+
...(output.mimeType ? { mimeType: output.mimeType } : {}),
|
|
534
|
+
})));
|
|
535
|
+
for (const url of result.message.mediaUrls) {
|
|
536
|
+
if (guessMimeType(url) !== undefined)
|
|
537
|
+
items.push({ url });
|
|
538
|
+
}
|
|
539
|
+
return items.filter(item => isWiroHostedUrl(item.url));
|
|
540
|
+
}
|
|
541
|
+
/** The tool result content: the text, then the media as resource links. */
|
|
542
|
+
export function turnContent(target, result) {
|
|
543
|
+
return createMediaContent(formatTurn(target, result), turnMediaItems(result));
|
|
544
|
+
}
|
|
545
|
+
function heading(target, result) {
|
|
546
|
+
if (result.message.status === NOT_VISIBLE_STATUS)
|
|
547
|
+
return '## Message not visible yet';
|
|
548
|
+
if (!result.done)
|
|
549
|
+
return `## ${target.name} is still working`;
|
|
550
|
+
switch (result.message.state) {
|
|
551
|
+
case 'completed':
|
|
552
|
+
return `## ${target.name} replied`;
|
|
553
|
+
case 'cancelled':
|
|
554
|
+
return '## The turn was cancelled';
|
|
555
|
+
case 'failed':
|
|
556
|
+
return '## The turn failed';
|
|
557
|
+
default:
|
|
558
|
+
return `## ${target.name}: ${result.message.status}`;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
export function formatTurn(target, result) {
|
|
562
|
+
const lines = [heading(target, result), ''];
|
|
563
|
+
lines.push(`**Agent:** \`${result.agent}\``);
|
|
564
|
+
lines.push(`**Session:** \`${result.session}\``);
|
|
565
|
+
if (result.llmCostUsd !== undefined && result.llmCostUsd !== null && result.llmCostUsd > 0) {
|
|
566
|
+
lines.push(`**Agent usage:** $${result.llmCostUsd}`);
|
|
567
|
+
}
|
|
568
|
+
if (result.modelChange?.toModel) {
|
|
569
|
+
lines.push(`**Model:** switched to \`${result.modelChange.toModel}\` for this session`);
|
|
570
|
+
}
|
|
571
|
+
if (result.attachedWorkflow) {
|
|
572
|
+
const { title, fullSlug } = result.attachedWorkflow;
|
|
573
|
+
lines.push(`**Attached workflow:** ${title ? `${title} (\`${fullSlug}\`)` : `\`${fullSlug}\``}`);
|
|
574
|
+
}
|
|
575
|
+
lines.push(formatAgentMessage(result.message));
|
|
576
|
+
if (result.partialReply) {
|
|
577
|
+
lines.push('', '### Partial reply', '', result.partialReply);
|
|
578
|
+
}
|
|
579
|
+
if (result.modelRuns && result.modelRuns.length > 0) {
|
|
580
|
+
lines.push('', '### Model runs started by this turn', '');
|
|
581
|
+
for (const run of result.modelRuns)
|
|
582
|
+
lines.push(formatStudioRun(run));
|
|
583
|
+
}
|
|
584
|
+
if (result.workflows && result.workflows.length > 0) {
|
|
585
|
+
lines.push('', '### Workflows in this reply', '');
|
|
586
|
+
for (const workflow of result.workflows) {
|
|
587
|
+
const name = workflow.title ? `**${workflow.title}**` : `Workflow ${workflow.index + 1}`;
|
|
588
|
+
const details = [
|
|
589
|
+
workflow.nodeCount !== undefined ? `${workflow.nodeCount} step${workflow.nodeCount === 1 ? '' : 's'}` : '',
|
|
590
|
+
workflowModelsText(workflow),
|
|
591
|
+
].filter(Boolean).join(', ');
|
|
592
|
+
const size = details ? ` (${details})` : '';
|
|
593
|
+
lines.push(`- ${name}${size}: ${workflow.valid ? 'valid' : workflow.status}`
|
|
594
|
+
+ (workflow.artifactHash ? `, artifact_hash \`${workflow.artifactHash}\`` : ''));
|
|
595
|
+
for (const issue of workflow.issues)
|
|
596
|
+
lines.push(` - ${issue}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (result.workflowList) {
|
|
600
|
+
lines.push('', '### Your workflows', '');
|
|
601
|
+
if (result.workflowList.error) {
|
|
602
|
+
lines.push(`The list could not be loaded: ${result.workflowList.error}`);
|
|
603
|
+
}
|
|
604
|
+
else if (result.workflowList.rows.length === 0) {
|
|
605
|
+
lines.push('No workflows matched.');
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
for (const row of result.workflowList.rows)
|
|
609
|
+
lines.push(`- **${row.title}**: \`${row.fullSlug}\``);
|
|
610
|
+
}
|
|
611
|
+
lines.push('', result.workflowList.instruction);
|
|
612
|
+
}
|
|
613
|
+
if (result.notes.length > 0) {
|
|
614
|
+
lines.push('', '### Notes', '');
|
|
615
|
+
for (const note of result.notes)
|
|
616
|
+
lines.push(`- ${note}`);
|
|
617
|
+
}
|
|
618
|
+
if (result.nextAction) {
|
|
619
|
+
lines.push('', `**Next:** \`${result.nextAction.tool}\` with \`${JSON.stringify(result.nextAction.arguments)}\`. ${result.nextAction.reason}`);
|
|
620
|
+
}
|
|
621
|
+
return lines.join('\n');
|
|
622
|
+
}
|
|
623
|
+
//# sourceMappingURL=agent-turns.js.map
|