(
paused: PausedExecution,
options?: { readonly deadline?: PausedExecutionDeadline },
)
| 211 | }; |
| 212 | |
| 213 | export const formatPausedExecution = ( |
| 214 | paused: PausedExecution, |
| 215 | options?: { readonly deadline?: PausedExecutionDeadline }, |
| 216 | ): { |
| 217 | text: string; |
| 218 | structured: Record<string, unknown>; |
| 219 | } => { |
| 220 | const req = paused.elicitationContext.request; |
| 221 | const lines: string[] = [`Execution paused: ${req.message}`]; |
| 222 | const deadline = options?.deadline; |
| 223 | const isUrlElicitation = Predicate.isTagged(req, "UrlElicitation"); |
| 224 | const isFormElicitation = Predicate.isTagged(req, "FormElicitation"); |
| 225 | const requestedSchema = isFormElicitation ? req.requestedSchema : undefined; |
| 226 | const hasRequestedSchema = |
| 227 | requestedSchema !== undefined && Object.keys(requestedSchema).length > 0; |
| 228 | const baseInstructions = isUrlElicitation |
| 229 | ? `The user needs to open this URL in a browser and complete the flow. After the user finishes, call the resume tool with executionId "${paused.id}" and action "accept".` |
| 230 | : hasRequestedSchema |
| 231 | ? `Ask the user for values matching requestedSchema. Then call the resume tool with executionId "${paused.id}", action "accept", and content matching requestedSchema. If the user declines, call resume with action "decline" or "cancel".` |
| 232 | : `This is a model-side confirmation gate; there is no browser form to open. Ask the user whether to approve the paused tool call. If the user approves, call the resume tool with executionId "${paused.id}" and action "accept". If the user declines, call resume with action "decline" or "cancel".`; |
| 233 | // When the upstream leaves the LIFETIME of an accept to the answer, the |
| 234 | // caller has to know that a bare accept is a one-time approval — the same |
| 235 | // prompt returns on the next call — and how to say otherwise. |
| 236 | const meta = req.meta; |
| 237 | const offered = offeredPersistence(meta); |
| 238 | const persistInstructions = |
| 239 | offered.length > 0 |
| 240 | ? ` To have an accepted approval remembered, also pass persist as one of ${offered |
| 241 | .map((scope) => JSON.stringify(scope)) |
| 242 | .join(", ")}; without it the approval is for this call only.` |
| 243 | : ""; |
| 244 | const deadlineInstructions = deadline |
| 245 | ? ` Resume before ${deadline.expiresAt}; this approval window lasts ${formatTtlDuration(deadline.ttlMs)}.` |
| 246 | : ""; |
| 247 | const instructions = `${baseInstructions}${persistInstructions}${deadlineInstructions}`; |
| 248 | |
| 249 | if (isUrlElicitation) { |
| 250 | lines.push(`\nOpen this URL in a browser:\n${req.url}`); |
| 251 | lines.push('\nAfter the browser flow, call the resume tool with action "accept".'); |
| 252 | } else if (hasRequestedSchema) { |
| 253 | lines.push( |
| 254 | "\nAsk the user for a response matching the requested schema, then call the resume tool.", |
| 255 | ); |
| 256 | lines.push(`\nRequested schema:\n${JSON.stringify(requestedSchema, null, 2)}`); |
| 257 | } else { |
| 258 | lines.push( |
| 259 | '\nThis is a model-side confirmation gate; no browser form is waiting. Ask the user whether to approve, then call the resume tool with action "accept", "decline", or "cancel".', |
| 260 | ); |
| 261 | } |
| 262 | |
| 263 | // Terms the upstream attached to the approval. Stated plainly, because a |
| 264 | // prompt whose schema is empty ("Allow X to access Y?") can still be |
| 265 | // asking for a PERSISTENT grant, and the answer differs. |
| 266 | if (meta !== undefined && Object.keys(meta).length > 0) { |
| 267 | lines.push(`\nApproval terms:\n${JSON.stringify(meta, null, 2)}`); |
| 268 | } |
| 269 | |
| 270 | lines.push(`\nexecutionId: ${paused.id}`); |
no test coverage detected