(args: ToolArgs, ctx: TrainingContext)
| 2599 | const MAX_SIGNAL_RESULTS = 10; |
| 2600 | |
| 2601 | export async function handleGetSignals(args: ToolArgs, ctx: TrainingContext) { |
| 2602 | const req = args as unknown as GetSignalsRequest & ToolArgs & { |
| 2603 | brief?: string; |
| 2604 | pagination?: { max_results?: number; cursor?: string }; |
| 2605 | }; |
| 2606 | // Accept both signal_spec (protocol) and brief (SDK test tool) |
| 2607 | const rawSpec = req.signal_spec || req.brief; |
| 2608 | const signalSpec = typeof rawSpec === 'string' ? rawSpec : undefined; |
| 2609 | // Pagination shape (pagination.max_results, schema cap 100) takes precedence |
| 2610 | // over the legacy top-level `max_results` (no schema cap; this handler |
| 2611 | // historically capped at 50 to keep semantic-search results focused). The two |
| 2612 | // forms have different caps because they have different contracts — |
| 2613 | // pagination.max_results is the standard envelope and matches the schema's |
| 2614 | // documented 100 cap; top-level max_results is the predecessor and we |
| 2615 | // preserve its tighter behavioral cap to avoid silently widening any caller |
| 2616 | // currently relying on the 50 ceiling. Spec ambiguity on which form wins |
| 2617 | // when both are present is tracked at adcontextprotocol/adcp#3113. |
| 2618 | let maxResults: number; |
| 2619 | const paginationMax = req.pagination?.max_results; |
| 2620 | if (typeof paginationMax === 'number' && paginationMax >= 1) { |
| 2621 | maxResults = Math.min(paginationMax, 100); |
| 2622 | } else if (typeof req.max_results === 'number' && req.max_results >= 1) { |
| 2623 | maxResults = Math.min(req.max_results, 50); |
| 2624 | } else { |
| 2625 | maxResults = MAX_SIGNAL_RESULTS; |
| 2626 | } |
| 2627 | const offset = decodeOffsetCursor('signals', req.pagination?.cursor); |
| 2628 | if (offset === null) { |
| 2629 | return { |
| 2630 | errors: [{ code: 'INVALID_REQUEST', message: 'pagination.cursor is malformed' }] as TaskError[], |
| 2631 | }; |
| 2632 | } |
| 2633 | const session = await getSession(sessionKeyFromArgs(req, ctx.mode, ctx.userId, ctx.moduleId)); |
| 2634 | |
| 2635 | const allSignals = getAllSignals(); |
| 2636 | let results = allSignals; |
| 2637 | |
| 2638 | // Exact lookup by signal_ids |
| 2639 | if (req.signal_ids?.length) { |
| 2640 | const idSet = new Set(req.signal_ids.map(sid => sid.id)); |
| 2641 | results = results.filter(s => idSet.has(s.signalAgentSegmentId)); |
| 2642 | } |
| 2643 | |
| 2644 | // Natural language search via signal_spec |
| 2645 | const rawTerms = signalSpec ? signalSpec.toLowerCase().split(/\s+/) : []; |
| 2646 | if (signalSpec) { |
| 2647 | const expanded = new Set<string>(); |
| 2648 | for (const t of rawTerms) { |
| 2649 | expanded.add(t); |
| 2650 | const synonyms = SYNONYM_MAP[t]; |
| 2651 | if (synonyms) { |
| 2652 | for (const s of synonyms) expanded.add(s); |
| 2653 | } |
| 2654 | } |
| 2655 | const terms = [...expanded]; |
| 2656 | const scored = results |
| 2657 | .map(s => { |
| 2658 | const text = `${s.name} ${s.description} ${s.tags.join(' ')} ${s.providerName}`.toLowerCase(); |
nothing calls this directly
no test coverage detected