MCPcopy Create free account
hub / github.com/adcontextprotocol/adcp / maybeEmitCompletionWebhook

Function maybeEmitCompletionWebhook

server/src/training-agent/webhooks.ts:161–202  ·  view source on GitHub ↗
(opts: {
  toolName: string;
  args: Record<string, unknown>;
  response: Record<string, unknown>;
  requestIdempotencyKey?: string;
  /** Caller-uniqueness key for webhook idempotency. Pass the same value the
   *  request-side idempotency store uses for this caller (legacy dispatch
   *  passes `scopedPrincipal(auth, accountScope)`; the framework path passes
   *  `auth` directly except for `static:public` where it scopes by account).
   *  Two distinct callers MUST produce distinct strings here, otherwise
   *  receivers that dedupe across tenants on `idempotency_key` may drop one
   *  caller's webhook as a duplicate of another's. Empty strings are rejected
   *  fail-fast — they would silently degrade scoping to "no partitioning". */
  principal: string;
})

Source from the content-addressed store, hash-verified

159 * adapter (`framework-server.ts`) so both paths emit byte-identical envelopes.
160 */
161export function maybeEmitCompletionWebhook(opts: {
162 toolName: string;
163 args: Record<string, unknown>;
164 response: Record<string, unknown>;
165 requestIdempotencyKey?: string;
166 /** Caller-uniqueness key for webhook idempotency. Pass the same value the
167 * request-side idempotency store uses for this caller (legacy dispatch
168 * passes `scopedPrincipal(auth, accountScope)`; the framework path passes
169 * `auth` directly except for `static:public` where it scopes by account).
170 * Two distinct callers MUST produce distinct strings here, otherwise
171 * receivers that dedupe across tenants on `idempotency_key` may drop one
172 * caller's webhook as a duplicate of another's. Empty strings are rejected
173 * fail-fast — they would silently degrade scoping to "no partitioning". */
174 principal: string;
175}): void {
176 if (!opts.principal) {
177 throw new Error('maybeEmitCompletionWebhook: principal must be a non-empty string (callers must pass the same caller-uniqueness key used for the request-side idempotency cache)');
178 }
179 const webhookUrl = extractWebhookUrl(opts.args);
180 if (!webhookUrl || !(opts.toolName in TOOL_TO_TASK_TYPE)) return;
181 const tool = opts.toolName as WebhookEmittingTool;
182
183 const emitter = getWebhookEmitter();
184 const registeredOperationId = extractRegisteredOperationId(opts.args);
185 const deliveryOperationId = registeredOperationId
186 ? deriveRegisteredWebhookDeliveryOperationId(opts.toolName, registeredOperationId, opts.principal)
187 : deriveWebhookOperationId(opts.toolName, opts.response, opts.requestIdempotencyKey, opts.principal);
188 const payloadOperationId = registeredOperationId ?? deliveryOperationId;
189 const webhookTaskId = (opts.response.task_id as string | undefined)
190 ?? `tsk_${payloadOperationId.slice(0, 32).replace(/[^A-Za-z0-9_.:-]/g, '_')}`;
191 const payload: Record<string, unknown> = {
192 operation_id: payloadOperationId,
193 task_id: webhookTaskId,
194 task_type: TOOL_TO_TASK_TYPE[tool],
195 protocol: TOOL_TO_PROTOCOL[tool],
196 status: 'completed',
197 timestamp: new Date().toISOString(),
198 result: opts.response,
199 };
200 void emitter.emit({ url: webhookUrl, payload, operation_id: deliveryOperationId })
201 .catch(err => logger.warn({ err, tool: opts.toolName, url: webhookUrl }, 'Webhook emission failed'));
202}
203
204const ENV_KEY = 'WEBHOOK_SIGNING_KEY_JWK';
205const KMS_WEBHOOK_ENV = 'GCP_KMS_WEBHOOK_KEY_VERSION';

Callers 2

dispatchCallToolFunction · 0.85
adaptFunction · 0.85

Calls 6

extractWebhookUrlFunction · 0.85
getWebhookEmitterFunction · 0.85
deriveWebhookOperationIdFunction · 0.85
warnMethod · 0.80

Tested by

no test coverage detected