@withpica/mcp-server 2.95.3 → 2.96.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/CHANGELOG.md +72 -0
- package/README.intro.md +115 -0
- package/README.md +188 -204
- package/dist/lib/changelog.generated.d.ts +2 -2
- package/dist/lib/changelog.generated.d.ts.map +1 -1
- package/dist/lib/changelog.generated.js +2 -2
- package/dist/lib/changelog.generated.js.map +1 -1
- package/dist/prompts/creator-question-atlas.d.ts.map +1 -1
- package/dist/prompts/creator-question-atlas.js +11 -0
- package/dist/prompts/creator-question-atlas.js.map +1 -1
- package/dist/prompts/index.js +2 -2
- package/dist/resources/agent-guide.d.ts +50 -4
- package/dist/resources/agent-guide.d.ts.map +1 -1
- package/dist/resources/agent-guide.js +109 -18
- package/dist/resources/agent-guide.js.map +1 -1
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +81 -2
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/llms-primer.d.ts +1 -1
- package/dist/resources/llms-primer.d.ts.map +1 -1
- package/dist/resources/llms-primer.js +1 -1
- package/dist/resources/required-schemas.generated.d.ts +3 -12
- package/dist/resources/required-schemas.generated.d.ts.map +1 -1
- package/dist/resources/required-schemas.generated.js +3 -18
- package/dist/resources/required-schemas.generated.js.map +1 -1
- package/dist/resources/required-schemas.source.d.ts.map +1 -1
- package/dist/resources/required-schemas.source.js +13 -15
- package/dist/resources/required-schemas.source.js.map +1 -1
- package/dist/server-instructions.d.ts +2 -2
- package/dist/server-instructions.d.ts.map +1 -1
- package/dist/server-instructions.js +3 -3
- package/dist/skills/skills.generated.d.ts.map +1 -1
- package/dist/skills/skills.generated.js +18 -9
- package/dist/skills/skills.generated.js.map +1 -1
- package/dist/tools/agent-identity.d.ts.map +1 -1
- package/dist/tools/agent-identity.js +2 -1
- package/dist/tools/agent-identity.js.map +1 -1
- package/dist/tools/agreement-types.d.ts.map +1 -1
- package/dist/tools/agreement-types.js +23 -11
- package/dist/tools/agreement-types.js.map +1 -1
- package/dist/tools/agreements.d.ts.map +1 -1
- package/dist/tools/agreements.js +17 -9
- package/dist/tools/agreements.js.map +1 -1
- package/dist/tools/analytics.d.ts.map +1 -1
- package/dist/tools/analytics.js +6 -3
- package/dist/tools/analytics.js.map +1 -1
- package/dist/tools/assets.d.ts.map +1 -1
- package/dist/tools/assets.js +20 -13
- package/dist/tools/assets.js.map +1 -1
- package/dist/tools/audio-files.d.ts.map +1 -1
- package/dist/tools/audio-files.js +5 -3
- package/dist/tools/audio-files.js.map +1 -1
- package/dist/tools/bulk.d.ts.map +1 -1
- package/dist/tools/bulk.js +15 -9
- package/dist/tools/bulk.js.map +1 -1
- package/dist/tools/collaborators.d.ts.map +1 -1
- package/dist/tools/collaborators.js +41 -16
- package/dist/tools/collaborators.js.map +1 -1
- package/dist/tools/comparisons.d.ts.map +1 -1
- package/dist/tools/comparisons.js +2 -1
- package/dist/tools/comparisons.js.map +1 -1
- package/dist/tools/credits.d.ts +4 -3
- package/dist/tools/credits.d.ts.map +1 -1
- package/dist/tools/credits.js +16 -88
- package/dist/tools/credits.js.map +1 -1
- package/dist/tools/custody.d.ts.map +1 -1
- package/dist/tools/custody.js +43 -10
- package/dist/tools/custody.js.map +1 -1
- package/dist/tools/dashboard.d.ts.map +1 -1
- package/dist/tools/dashboard.js +24 -20
- package/dist/tools/dashboard.js.map +1 -1
- package/dist/tools/deprecations.d.ts +14 -7
- package/dist/tools/deprecations.d.ts.map +1 -1
- package/dist/tools/deprecations.js +24 -17
- package/dist/tools/deprecations.js.map +1 -1
- package/dist/tools/directory.d.ts.map +1 -1
- package/dist/tools/directory.js +7 -4
- package/dist/tools/directory.js.map +1 -1
- package/dist/tools/discovery.d.ts +120 -11
- package/dist/tools/discovery.d.ts.map +1 -1
- package/dist/tools/discovery.js +414 -38
- package/dist/tools/discovery.js.map +1 -1
- package/dist/tools/disputes.d.ts.map +1 -1
- package/dist/tools/disputes.js +3 -0
- package/dist/tools/disputes.js.map +1 -1
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +9 -6
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/enrichment.d.ts.map +1 -1
- package/dist/tools/enrichment.js +92 -14
- package/dist/tools/enrichment.js.map +1 -1
- package/dist/tools/error-envelope.d.ts +44 -0
- package/dist/tools/error-envelope.d.ts.map +1 -0
- package/dist/tools/error-envelope.js +17 -0
- package/dist/tools/error-envelope.js.map +1 -0
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/exports.js +24 -9
- package/dist/tools/exports.js.map +1 -1
- package/dist/tools/groups.d.ts.map +1 -1
- package/dist/tools/groups.js +1 -0
- package/dist/tools/groups.js.map +1 -1
- package/dist/tools/import-documents.d.ts.map +1 -1
- package/dist/tools/import-documents.js +3 -2
- package/dist/tools/import-documents.js.map +1 -1
- package/dist/tools/import.d.ts.map +1 -1
- package/dist/tools/import.js +71 -38
- package/dist/tools/import.js.map +1 -1
- package/dist/tools/index.d.ts +71 -4
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +160 -41
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/licensing.d.ts.map +1 -1
- package/dist/tools/licensing.js +6 -2
- package/dist/tools/licensing.js.map +1 -1
- package/dist/tools/memory.d.ts.map +1 -1
- package/dist/tools/memory.js +8 -4
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/metadata.d.ts +7 -1
- package/dist/tools/metadata.d.ts.map +1 -1
- package/dist/tools/metadata.js +61 -12
- package/dist/tools/metadata.js.map +1 -1
- package/dist/tools/multimedia.d.ts.map +1 -1
- package/dist/tools/multimedia.js +7 -3
- package/dist/tools/multimedia.js.map +1 -1
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +9 -6
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notifications.d.ts.map +1 -1
- package/dist/tools/notifications.js +16 -27
- package/dist/tools/notifications.js.map +1 -1
- package/dist/tools/people.d.ts.map +1 -1
- package/dist/tools/people.js +8 -5
- package/dist/tools/people.js.map +1 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +16 -12
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/recording-custody.d.ts.map +1 -1
- package/dist/tools/recording-custody.js +9 -5
- package/dist/tools/recording-custody.js.map +1 -1
- package/dist/tools/recording-samples.d.ts.map +1 -1
- package/dist/tools/recording-samples.js +25 -6
- package/dist/tools/recording-samples.js.map +1 -1
- package/dist/tools/recordings.d.ts +10 -0
- package/dist/tools/recordings.d.ts.map +1 -1
- package/dist/tools/recordings.js +32 -19
- package/dist/tools/recordings.js.map +1 -1
- package/dist/tools/recovery-hints.d.ts.map +1 -1
- package/dist/tools/recovery-hints.js +58 -18
- package/dist/tools/recovery-hints.js.map +1 -1
- package/dist/tools/release-rich.js +5 -5
- package/dist/tools/release-rich.js.map +1 -1
- package/dist/tools/releases.d.ts.map +1 -1
- package/dist/tools/releases.js +21 -12
- package/dist/tools/releases.js.map +1 -1
- package/dist/tools/royalties.d.ts.map +1 -1
- package/dist/tools/royalties.js +12 -6
- package/dist/tools/royalties.js.map +1 -1
- package/dist/tools/send.d.ts.map +1 -1
- package/dist/tools/send.js +7 -4
- package/dist/tools/send.js.map +1 -1
- package/dist/tools/sessions.d.ts.map +1 -1
- package/dist/tools/sessions.js +8 -4
- package/dist/tools/sessions.js.map +1 -1
- package/dist/tools/settings.d.ts.map +1 -1
- package/dist/tools/settings.js +13 -6
- package/dist/tools/settings.js.map +1 -1
- package/dist/tools/share-links.d.ts.map +1 -1
- package/dist/tools/share-links.js +7 -3
- package/dist/tools/share-links.js.map +1 -1
- package/dist/tools/share-send.d.ts.map +1 -1
- package/dist/tools/share-send.js +15 -5
- package/dist/tools/share-send.js.map +1 -1
- package/dist/tools/shows.d.ts +31 -0
- package/dist/tools/shows.d.ts.map +1 -0
- package/dist/tools/shows.js +307 -0
- package/dist/tools/shows.js.map +1 -0
- package/dist/tools/skills.d.ts.map +1 -1
- package/dist/tools/skills.js +7 -0
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/split-sheets.d.ts.map +1 -1
- package/dist/tools/split-sheets.js +19 -12
- package/dist/tools/split-sheets.js.map +1 -1
- package/dist/tools/statements.js +2 -2
- package/dist/tools/statements.js.map +1 -1
- package/dist/tools/storage-config.d.ts.map +1 -1
- package/dist/tools/storage-config.js +5 -3
- package/dist/tools/storage-config.js.map +1 -1
- package/dist/tools/subscription.d.ts +162 -26
- package/dist/tools/subscription.d.ts.map +1 -1
- package/dist/tools/subscription.js +479 -241
- package/dist/tools/subscription.js.map +1 -1
- package/dist/tools/sync-placements.d.ts.map +1 -1
- package/dist/tools/sync-placements.js +10 -5
- package/dist/tools/sync-placements.js.map +1 -1
- package/dist/tools/telegram.d.ts.map +1 -1
- package/dist/tools/telegram.js +16 -24
- package/dist/tools/telegram.js.map +1 -1
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +12 -6
- package/dist/tools/users.js.map +1 -1
- package/dist/tools/works.d.ts.map +1 -1
- package/dist/tools/works.js +13 -10
- package/dist/tools/works.js.map +1 -1
- package/package.json +8 -4
- package/server.json +2 -2
- package/.claude/settings.local.json +0 -5
package/dist/tools/discovery.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
// Copyright (c) 2025-2026 Withpica Ltd. All rights reserved.
|
|
2
|
+
// `deriveAnnotations` is a value import back into ./index.js, which imports
|
|
3
|
+
// this module — an ESM cycle. Safe because it is a hoisted function
|
|
4
|
+
// declaration: it is initialised before either module body runs, and it is
|
|
5
|
+
// only ever CALLED from inside a method here. Do not convert it to a const
|
|
6
|
+
// arrow function on the other side.
|
|
7
|
+
import { deriveAnnotations } from "./index.js";
|
|
8
|
+
import { getAliasFor } from "./deprecations.js";
|
|
2
9
|
import { getToolMetadata } from "./metadata.js";
|
|
3
10
|
import { RECOVERY_HINTS } from "./recovery-hints.js";
|
|
4
11
|
const SCHEMA_VERSION = "2026.1";
|
|
@@ -25,6 +32,133 @@ const SCOPES_NOT_GRANTABLE_TO_STANDARD_AGENT = new Set([
|
|
|
25
32
|
]);
|
|
26
33
|
/** Max matched tools returned by a `pica_discover({ query })` call. */
|
|
27
34
|
const DISCOVER_MATCH_LIMIT = 25;
|
|
35
|
+
/**
|
|
36
|
+
* The fresh-org on-ramp, carried by BOTH pica_discover modes (ADR-294 L3).
|
|
37
|
+
* Static and org-blind by design so the response stays cacheable: the
|
|
38
|
+
* instruction is universal, not org data. One constant, because the ChatGPT
|
|
39
|
+
* app moved to query mode on 2026-08-10 and for sixteen days this line lived
|
|
40
|
+
* only on the full map — the client carrying 92% of tool calls never saw it
|
|
41
|
+
* (FIX_LOG 2026-08-26). Pinned in discovery.test.ts for a hit, a miss and
|
|
42
|
+
* the full map.
|
|
43
|
+
*/
|
|
44
|
+
export const FRESH_ORG_HINT = "on a fresh or empty org, run pica_dashboard_briefing first (through pica_execute where this surface routes calls that way). if the catalog is empty it names the fastest import: paste a spotify album or playlist link.";
|
|
45
|
+
/**
|
|
46
|
+
* Words that describe the asker, not the entity. `pica_my_*` tools carry
|
|
47
|
+
* "my" as their DOMAIN segment, so before this filter "start my music
|
|
48
|
+
* catalogue and import my songs" led with pica_my_recent_questions,
|
|
49
|
+
* pica_my_reported_issues and pica_export_my_data — three tools about the
|
|
50
|
+
* caller, for a user who asked to import (prod agent_events, 2026-08-11).
|
|
51
|
+
* Determiners, pronouns, prepositions, conjunctions, auxiliaries and the
|
|
52
|
+
* LIGHT verbs (get / list / find / show / check / run / set …) that name no
|
|
53
|
+
* entity — "get the isrc" is about the ISRC. Eight tools carry a light verb
|
|
54
|
+
* as their first segment (pica_get_agent_activity, pica_find_duplicates,
|
|
55
|
+
* pica_run_work_cascade …); for them the entity is the next segment, and
|
|
56
|
+
* the verb still reaches them through the name text (+1). Domain verbs that
|
|
57
|
+
* discriminate inside a domain (delete, create, update, query, import …)
|
|
58
|
+
* stay. Before the light verbs were listed, "get the isrc from this
|
|
59
|
+
* spotify link" led with pica_get_agent_activity once the noise words that
|
|
60
|
+
* had been propping the import tool up were removed.
|
|
61
|
+
*/
|
|
62
|
+
export const STOP_WORDS = new Set([
|
|
63
|
+
"a",
|
|
64
|
+
"an",
|
|
65
|
+
"the",
|
|
66
|
+
"my",
|
|
67
|
+
"me",
|
|
68
|
+
"mine",
|
|
69
|
+
"our",
|
|
70
|
+
"ours",
|
|
71
|
+
"your",
|
|
72
|
+
"yours",
|
|
73
|
+
"his",
|
|
74
|
+
"her",
|
|
75
|
+
"hers",
|
|
76
|
+
"their",
|
|
77
|
+
"theirs",
|
|
78
|
+
"its",
|
|
79
|
+
"it",
|
|
80
|
+
"i",
|
|
81
|
+
"we",
|
|
82
|
+
"you",
|
|
83
|
+
"they",
|
|
84
|
+
"them",
|
|
85
|
+
"us",
|
|
86
|
+
"this",
|
|
87
|
+
"that",
|
|
88
|
+
"these",
|
|
89
|
+
"those",
|
|
90
|
+
"and",
|
|
91
|
+
"or",
|
|
92
|
+
"of",
|
|
93
|
+
"to",
|
|
94
|
+
"for",
|
|
95
|
+
"in",
|
|
96
|
+
"on",
|
|
97
|
+
"at",
|
|
98
|
+
"by",
|
|
99
|
+
"with",
|
|
100
|
+
"from",
|
|
101
|
+
"into",
|
|
102
|
+
"across",
|
|
103
|
+
"about",
|
|
104
|
+
"as",
|
|
105
|
+
"per",
|
|
106
|
+
"is",
|
|
107
|
+
"are",
|
|
108
|
+
"am",
|
|
109
|
+
"be",
|
|
110
|
+
"was",
|
|
111
|
+
"were",
|
|
112
|
+
"do",
|
|
113
|
+
"does",
|
|
114
|
+
"did",
|
|
115
|
+
"can",
|
|
116
|
+
"could",
|
|
117
|
+
"would",
|
|
118
|
+
"should",
|
|
119
|
+
"will",
|
|
120
|
+
"shall",
|
|
121
|
+
"please",
|
|
122
|
+
"want",
|
|
123
|
+
"need",
|
|
124
|
+
"just",
|
|
125
|
+
"also",
|
|
126
|
+
"some",
|
|
127
|
+
"any",
|
|
128
|
+
"all",
|
|
129
|
+
"get",
|
|
130
|
+
"list",
|
|
131
|
+
"find",
|
|
132
|
+
"show",
|
|
133
|
+
"see",
|
|
134
|
+
"view",
|
|
135
|
+
"check",
|
|
136
|
+
"run",
|
|
137
|
+
"make",
|
|
138
|
+
"use",
|
|
139
|
+
"look",
|
|
140
|
+
"set",
|
|
141
|
+
]);
|
|
142
|
+
/**
|
|
143
|
+
* Rows reserved at the tail of a capped shortlist for tools the caller
|
|
144
|
+
* cannot run here — a floor, not a ceiling: flagged rows also fill whatever
|
|
145
|
+
* the visible rows leave free, so a narrowly-scoped connection sees as many
|
|
146
|
+
* rows as an open one. Visible-first sorting alone let them fall off the
|
|
147
|
+
* end of a 25-row list, so an agent could conclude a capability was missing
|
|
148
|
+
* rather than gated (the Ridwayne session: five finance tools, zero
|
|
149
|
+
* callable). Pinned by discovery-ranking.test.ts.
|
|
150
|
+
*/
|
|
151
|
+
const FLAGGED_MATCH_ROWS = 5;
|
|
152
|
+
/**
|
|
153
|
+
* Flag text for a tool the caller cannot run on this connection — surfaced
|
|
154
|
+
* as `surface_note` beside `callable_on_this_surface: false` in the
|
|
155
|
+
* shortlist row and in tool_details, so an agent learns the wall at
|
|
156
|
+
* discovery time. Its own key: a scope or destructive-tier `callable_note`
|
|
157
|
+
* carries the specific remediation (admin elevation, confirmation token)
|
|
158
|
+
* and must survive alongside it. Factual; names the two causes the
|
|
159
|
+
* predicate folds together (ADR-248: audiences ∋ surface AND scope held).
|
|
160
|
+
*/
|
|
161
|
+
const NOT_CALLABLE_ON_SURFACE_NOTE = "not callable on this connection — it is listed on a different surface (stdio / lobby / web) or needs a scope this connection does not hold. prefer a tool without this note; the user can reach it from withpica.com.";
|
|
28
162
|
/**
|
|
29
163
|
* Hand-maintained category taxonomy for the catalog server.
|
|
30
164
|
* Grouped by natural entity/capability prefix.
|
|
@@ -507,8 +641,17 @@ export const CATEGORIES = {
|
|
|
507
641
|
depends_on: [],
|
|
508
642
|
},
|
|
509
643
|
subscription: {
|
|
510
|
-
|
|
511
|
-
|
|
644
|
+
// Reworded 2026-08-26 (WS-B). "pick a tier" described a capacity ladder
|
|
645
|
+
// ADR-276 superseded, and neither "what does pica cost" nor "how do I pay"
|
|
646
|
+
// appeared anywhere in this bucket — so the two questions a paying user
|
|
647
|
+
// actually asks routed to no category at all.
|
|
648
|
+
description: "what does pica cost, how do I pay, and what is pica holding — read " +
|
|
649
|
+
"your billing state and get a pay link",
|
|
650
|
+
tools: [
|
|
651
|
+
"pica_subscription_status",
|
|
652
|
+
"pica_billing_pay",
|
|
653
|
+
"pica_subscription_manage",
|
|
654
|
+
],
|
|
512
655
|
depends_on: [],
|
|
513
656
|
},
|
|
514
657
|
profile: {
|
|
@@ -811,6 +954,11 @@ export const CATEGORIES = {
|
|
|
811
954
|
],
|
|
812
955
|
depends_on: [],
|
|
813
956
|
},
|
|
957
|
+
shows: {
|
|
958
|
+
description: "log live shows and tour credits, and confirm setlist matches against your catalogue",
|
|
959
|
+
tools: ["pica_log_show", "pica_shows_query", "pica_setlist_match"],
|
|
960
|
+
depends_on: [],
|
|
961
|
+
},
|
|
814
962
|
};
|
|
815
963
|
/**
|
|
816
964
|
* Operational metadata defaults for pica_tool_details responses.
|
|
@@ -827,49 +975,167 @@ function getOperationalMeta(toolName) {
|
|
|
827
975
|
}
|
|
828
976
|
return {};
|
|
829
977
|
}
|
|
978
|
+
/**
|
|
979
|
+
* Naive English singular of a search token.
|
|
980
|
+
*
|
|
981
|
+
* A wrong stem is NOT free. An `-es` strip turns `notes` into `not` — and
|
|
982
|
+
* `pica_notes_*` are in CATEGORIES with `notes` as their domain segment, so
|
|
983
|
+
* every query containing the ordinary English word "not" ("works not
|
|
984
|
+
* registered") scored a +6 domain hit on the notes family. The same rule
|
|
985
|
+
* stems `releases`→`releas`, which the query word `release` can never match,
|
|
986
|
+
* defeating the feature in the other direction. Both shipped on 2026-08-24
|
|
987
|
+
* and are pinned by the singularise unit tests.
|
|
988
|
+
*
|
|
989
|
+
* The rules, in order:
|
|
990
|
+
* - `ies` → `y` (categories → category)
|
|
991
|
+
* - `s` → "" unless the token ends `ss`/`us`/`is`, none of which is an
|
|
992
|
+
* English plural (address, status, analysis stay put)
|
|
993
|
+
*
|
|
994
|
+
* There is deliberately NO `-es` rule. Every domain segment in CATEGORIES is a
|
|
995
|
+
* plain `-s` plural (works, notes, releases, documents, statements, credits,
|
|
996
|
+
* shows…), so `-es` would only ever misfire: it cannot tell `releas+es` from
|
|
997
|
+
* `release+s`, and the wrong branch (`releas`) is one a query word can never
|
|
998
|
+
* reach. `boxes` therefore stems to `boxe` rather than `box` — harmless,
|
|
999
|
+
* because both sides of every comparison run through this same function.
|
|
1000
|
+
*
|
|
1001
|
+
* Exported for direct unit assertion in
|
|
1002
|
+
* `src/__tests__/tools/discovery-ranking.test.ts` — the stem is load-bearing
|
|
1003
|
+
* enough that it needs testing without a whole registry behind it.
|
|
1004
|
+
*/
|
|
1005
|
+
export function singularise(token) {
|
|
1006
|
+
if (token.length > 3 && token.endsWith("ies"))
|
|
1007
|
+
return `${token.slice(0, -3)}y`;
|
|
1008
|
+
if (token.length > 2 && token.endsWith("s") && !/(?:ss|us|is)$/.test(token))
|
|
1009
|
+
return token.slice(0, -1);
|
|
1010
|
+
return token;
|
|
1011
|
+
}
|
|
1012
|
+
/** The token plus its stem, deduped — matching on either is a hit. */
|
|
1013
|
+
function tokenVariants(token) {
|
|
1014
|
+
const stem = singularise(token);
|
|
1015
|
+
return stem === token ? [token] : [token, stem];
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Levenshtein distance, two rolling rows. Tool names are short (< 60 chars)
|
|
1019
|
+
* and the registry is a few hundred entries, so the O(n·m) form is well
|
|
1020
|
+
* inside budget for a once-per-failed-dispatch call.
|
|
1021
|
+
*/
|
|
1022
|
+
function editDistance(a, b) {
|
|
1023
|
+
if (a === b)
|
|
1024
|
+
return 0;
|
|
1025
|
+
if (a.length === 0)
|
|
1026
|
+
return b.length;
|
|
1027
|
+
if (b.length === 0)
|
|
1028
|
+
return a.length;
|
|
1029
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1030
|
+
let curr = new Array(b.length + 1);
|
|
1031
|
+
for (let i = 1; i <= a.length; i++) {
|
|
1032
|
+
curr[0] = i;
|
|
1033
|
+
for (let j = 1; j <= b.length; j++) {
|
|
1034
|
+
const substitution = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
1035
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, substitution);
|
|
1036
|
+
}
|
|
1037
|
+
[prev, curr] = [curr, prev];
|
|
1038
|
+
}
|
|
1039
|
+
return prev[b.length];
|
|
1040
|
+
}
|
|
1041
|
+
/** Length of the common leading substring — a typo late in a name keeps it. */
|
|
1042
|
+
function sharedPrefixLength(a, b) {
|
|
1043
|
+
const max = Math.min(a.length, b.length);
|
|
1044
|
+
let i = 0;
|
|
1045
|
+
while (i < max && a[i] === b[i])
|
|
1046
|
+
i++;
|
|
1047
|
+
return i;
|
|
1048
|
+
}
|
|
830
1049
|
export class DiscoveryTools {
|
|
831
1050
|
executeToolFn;
|
|
832
1051
|
getToolDef;
|
|
833
1052
|
listToolNames;
|
|
1053
|
+
isToolVisible;
|
|
834
1054
|
constructor(executeToolFn, getToolDef,
|
|
835
1055
|
/**
|
|
836
|
-
* Registered tool names, for
|
|
1056
|
+
* Registered tool names, for TOOL_NOT_FOUND near-match suggestions.
|
|
837
1057
|
* Optional so existing constructions keep compiling; without it the
|
|
838
1058
|
* unknown-tool envelope simply carries no suggestions.
|
|
839
1059
|
*/
|
|
840
|
-
listToolNames
|
|
1060
|
+
listToolNames,
|
|
1061
|
+
/**
|
|
1062
|
+
* Whether the CURRENT caller can run a tool on this connection. On HTTP
|
|
1063
|
+
* the route installs its `allowedToolNames` judgement (ADR-248:
|
|
1064
|
+
* audiences ∋ http AND scope held) via `ToolRegistry.setToolVisibility`;
|
|
1065
|
+
* the registry's default is audience-only. Absent = every registered
|
|
1066
|
+
* tool is treated as callable (legacy constructions, unit harnesses).
|
|
1067
|
+
* Consulted by the shortlist (rank visible first, flag the rest) and by
|
|
1068
|
+
* tool_details (flag) — never by pica_execute, which the dispatcher
|
|
1069
|
+
* already gates.
|
|
1070
|
+
*/
|
|
1071
|
+
isToolVisible) {
|
|
841
1072
|
this.executeToolFn = executeToolFn;
|
|
842
1073
|
this.getToolDef = getToolDef;
|
|
843
1074
|
this.listToolNames = listToolNames;
|
|
1075
|
+
this.isToolVisible = isToolVisible;
|
|
1076
|
+
}
|
|
1077
|
+
isVisible(toolName) {
|
|
1078
|
+
return this.isToolVisible ? this.isToolVisible(toolName) : true;
|
|
1079
|
+
}
|
|
1080
|
+
/** Shortlist / detail keys added only when the tool is NOT callable here. */
|
|
1081
|
+
surfaceFlags(toolName) {
|
|
1082
|
+
return this.isVisible(toolName)
|
|
1083
|
+
? {}
|
|
1084
|
+
: {
|
|
1085
|
+
callable_on_this_surface: false,
|
|
1086
|
+
surface_note: NOT_CALLABLE_ON_SURFACE_NOTE,
|
|
1087
|
+
};
|
|
844
1088
|
}
|
|
845
1089
|
/**
|
|
846
|
-
* Rank registered tool names
|
|
847
|
-
*
|
|
1090
|
+
* Rank registered tool names against a hallucinated or mistyped one.
|
|
1091
|
+
*
|
|
1092
|
+
* ChatGPT callers routinely invent tool names
|
|
848
1093
|
* (`pica_releases_attach_track_by_identifier`, `pica_credit_add`, …) and
|
|
849
1094
|
* retry variations instead of consulting pica_tool_details — 26 opaque
|
|
850
1095
|
* dispatcher errors in 14 days. Naming the closest real tools breaks that
|
|
851
1096
|
* guess-loop.
|
|
1097
|
+
*
|
|
1098
|
+
* Token overlap alone was not enough: `pica_works_inspekt` overlaps every
|
|
1099
|
+
* `pica_works_*` tool on exactly one token, so the top-3 slice fell back to
|
|
1100
|
+
* registration order and could drop the obvious answer. Edit distance
|
|
1101
|
+
* breaks that tie — pinned by "ranks the nearest EDIT-distance name first"
|
|
1102
|
+
* in `src/__tests__/tools/discovery.test.ts`.
|
|
1103
|
+
*
|
|
1104
|
+
* Deprecated aliases are excluded. The candidate pool is
|
|
1105
|
+
* `[...this.tools.keys()]` (index.ts, where DiscoveryTools is constructed),
|
|
1106
|
+
* which includes the hidden alias clones `expandAliases` registers — so a
|
|
1107
|
+
* typo one letter off an alias (`pica_releases_lists`) was answered with the
|
|
1108
|
+
* alias (`pica_releases_list`, removal_target 2.96.0) ranked above its own
|
|
1109
|
+
* canonical. Steering a confused caller onto a name we are trying to retire
|
|
1110
|
+
* is the opposite of what this envelope is for.
|
|
852
1111
|
*/
|
|
853
1112
|
suggestToolNames(input) {
|
|
854
|
-
const candidates = this.listToolNames?.() ?? [];
|
|
1113
|
+
const candidates = (this.listToolNames?.() ?? []).filter((name) => getAliasFor(name) === undefined);
|
|
855
1114
|
const tokens = new Set(input
|
|
856
1115
|
.replace(/^pica_/, "")
|
|
857
1116
|
.split("_")
|
|
858
1117
|
.filter(Boolean));
|
|
859
1118
|
if (tokens.size === 0 || candidates.length === 0)
|
|
860
1119
|
return [];
|
|
861
|
-
return candidates
|
|
1120
|
+
return (candidates
|
|
862
1121
|
.map((name) => ({
|
|
863
1122
|
name,
|
|
864
1123
|
overlap: name
|
|
865
1124
|
.replace(/^pica_/, "")
|
|
866
1125
|
.split("_")
|
|
867
1126
|
.filter((t) => tokens.has(t)).length,
|
|
1127
|
+
distance: editDistance(name, input),
|
|
1128
|
+
prefix: sharedPrefixLength(name, input),
|
|
868
1129
|
}))
|
|
869
|
-
|
|
870
|
-
|
|
1130
|
+
// A near-miss on spelling shares no whole token, so distance and prefix
|
|
1131
|
+
// have to be able to admit a candidate on their own.
|
|
1132
|
+
.filter((c) => c.overlap > 0 || c.distance <= 3 || c.prefix >= 8)
|
|
1133
|
+
.sort((a, b) => b.overlap - a.overlap ||
|
|
1134
|
+
a.distance - b.distance ||
|
|
1135
|
+
b.prefix - a.prefix ||
|
|
1136
|
+
a.name.localeCompare(b.name))
|
|
871
1137
|
.slice(0, 3)
|
|
872
|
-
.map((c) => c.name);
|
|
1138
|
+
.map((c) => c.name));
|
|
873
1139
|
}
|
|
874
1140
|
getTools() {
|
|
875
1141
|
return [
|
|
@@ -911,6 +1177,10 @@ export class DiscoveryTools {
|
|
|
911
1177
|
},
|
|
912
1178
|
executor: async (args) => {
|
|
913
1179
|
const cacheUntil = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
|
|
1180
|
+
// Query-mode rows carry per-connection flags since 2026-08-26 (an
|
|
1181
|
+
// org's finance toggle changes them), so a shortlist is cacheable for
|
|
1182
|
+
// an hour, not the day the org-blind full map gets.
|
|
1183
|
+
const queryCacheUntil = new Date(Date.now() + 60 * 60 * 1000).toISOString();
|
|
914
1184
|
const rawQuery = typeof args?.query === "string" ? args.query : "";
|
|
915
1185
|
const query = rawQuery.trim();
|
|
916
1186
|
// Filtered mode — a job-search shortlist. Additive: when no query is
|
|
@@ -923,18 +1193,20 @@ export class DiscoveryTools {
|
|
|
923
1193
|
];
|
|
924
1194
|
const payload = {
|
|
925
1195
|
schema_version: SCHEMA_VERSION,
|
|
926
|
-
cache_until:
|
|
1196
|
+
cache_until: queryCacheUntil,
|
|
927
1197
|
query,
|
|
928
1198
|
match_count: matches.length,
|
|
929
1199
|
matches: matches.map(({ tool, category, description }) => ({
|
|
930
1200
|
tool,
|
|
931
1201
|
category,
|
|
932
1202
|
description,
|
|
1203
|
+
...this.surfaceFlags(tool),
|
|
933
1204
|
})),
|
|
934
1205
|
matched_categories: matchedCategories,
|
|
935
|
-
hint: matches.length > 0
|
|
1206
|
+
hint: (matches.length > 0
|
|
936
1207
|
? `${matches.length} tool(s) match "${query}". Call pica_tool_details({ tool: "<name>" }) for a full schema. Call pica_discover with no query to browse all ${Object.keys(CATEGORIES).length} categories.`
|
|
937
|
-
: `no tools matched "${query}". Try different words, or call pica_discover with no query to browse all ${Object.keys(CATEGORIES).length} categories before concluding a capability is missing
|
|
1208
|
+
: `no tools matched "${query}". Try different words, or call pica_discover with no query to browse all ${Object.keys(CATEGORIES).length} categories before concluding a capability is missing.`) +
|
|
1209
|
+
` ${FRESH_ORG_HINT}`,
|
|
938
1210
|
};
|
|
939
1211
|
return {
|
|
940
1212
|
content: [
|
|
@@ -962,7 +1234,7 @@ export class DiscoveryTools {
|
|
|
962
1234
|
cache_until: cacheUntil,
|
|
963
1235
|
// Static, org-blind by design (the response is cacheable): the
|
|
964
1236
|
// fresh-org on-ramp is a universal instruction, not org data.
|
|
965
|
-
hint:
|
|
1237
|
+
hint: FRESH_ORG_HINT,
|
|
966
1238
|
categories,
|
|
967
1239
|
};
|
|
968
1240
|
return {
|
|
@@ -979,18 +1251,32 @@ export class DiscoveryTools {
|
|
|
979
1251
|
}
|
|
980
1252
|
/**
|
|
981
1253
|
* Rank tools against a plain-language job description. Case-insensitive
|
|
982
|
-
* token scoring (no external deps):
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
1254
|
+
* token scoring (no external deps):
|
|
1255
|
+
*
|
|
1256
|
+
* +6 the token matches the tool's DOMAIN segment (the `_`-separated
|
|
1257
|
+
* segment after `pica_` — `recordings` in `pica_recordings_delete`)
|
|
1258
|
+
* +3 the token matches any other `_`-separated segment of the name
|
|
1259
|
+
* +1 the token appears anywhere in the searchable text (name +
|
|
1260
|
+
* description/triggers + category name + category description)
|
|
1261
|
+
*
|
|
1262
|
+
* Both sides are compared through {@link singularise}, so "recording" and
|
|
1263
|
+
* "recordings" are one term. Tools are deduped across cross-listed
|
|
1264
|
+
* categories (keeping the highest-scoring category), sorted by score then
|
|
1265
|
+
* name, and capped.
|
|
1266
|
+
*
|
|
1267
|
+
* The domain weight exists because the VERB is shared by dozens of tools
|
|
1268
|
+
* and the ENTITY is what the user actually named: before it, "delete a
|
|
1269
|
+
* recording" led with `pica_agreement_types_delete` and "read a document"
|
|
1270
|
+
* put `pica_notifications_mark_read` above every documents tool. Both are
|
|
1271
|
+
* pinned in `src/__tests__/tools/discovery-ranking.test.ts`, alongside the
|
|
1272
|
+
* flagship isrc-from-spotify pin the weighting must not disturb.
|
|
988
1273
|
*/
|
|
989
1274
|
matchDiscovery(query) {
|
|
990
1275
|
const tokens = query
|
|
991
1276
|
.toLowerCase()
|
|
992
1277
|
.split(/[^a-z0-9]+/)
|
|
993
|
-
.filter((t) => t.length >= 2)
|
|
1278
|
+
.filter((t) => t.length >= 2 && !STOP_WORDS.has(t))
|
|
1279
|
+
.map((t) => ({ raw: t, variants: tokenVariants(t) }));
|
|
994
1280
|
if (tokens.length === 0)
|
|
995
1281
|
return [];
|
|
996
1282
|
// Best score per tool, plus the category that produced it.
|
|
@@ -1001,16 +1287,22 @@ export class DiscoveryTools {
|
|
|
1001
1287
|
const def = this.getToolDef(toolName)?.definition;
|
|
1002
1288
|
const metadata = getToolMetadata(toolName);
|
|
1003
1289
|
const toolDesc = def?.description ?? metadata?.display_name ?? "";
|
|
1004
|
-
const
|
|
1290
|
+
const segments = toolName
|
|
1005
1291
|
.replace(/^pica_/, "")
|
|
1006
1292
|
.split("_")
|
|
1007
|
-
.filter(Boolean)
|
|
1293
|
+
.filter(Boolean);
|
|
1294
|
+
// The entity the tool acts on. `pica_upload` has no verb segment, so
|
|
1295
|
+
// its only segment is both domain and verb — that is correct here.
|
|
1296
|
+
const domainVariants = new Set(segments.length > 0 ? tokenVariants(segments[0]) : []);
|
|
1297
|
+
const nameVariants = new Set(segments.flatMap(tokenVariants));
|
|
1008
1298
|
const haystack = `${toolName.replace(/_/g, " ")} ${toolDesc} ${catText}`.toLowerCase();
|
|
1009
1299
|
let score = 0;
|
|
1010
|
-
for (const
|
|
1011
|
-
if (
|
|
1300
|
+
for (const { variants } of tokens) {
|
|
1301
|
+
if (variants.some((v) => domainVariants.has(v)))
|
|
1302
|
+
score += 6;
|
|
1303
|
+
else if (variants.some((v) => nameVariants.has(v)))
|
|
1012
1304
|
score += 3;
|
|
1013
|
-
if (haystack.includes(
|
|
1305
|
+
if (variants.some((v) => haystack.includes(v)))
|
|
1014
1306
|
score += 1;
|
|
1015
1307
|
}
|
|
1016
1308
|
if (score === 0)
|
|
@@ -1022,13 +1314,24 @@ export class DiscoveryTools {
|
|
|
1022
1314
|
category: key,
|
|
1023
1315
|
description: this.oneLineDescription(toolName),
|
|
1024
1316
|
score,
|
|
1317
|
+
visible: this.isVisible(toolName),
|
|
1025
1318
|
});
|
|
1026
1319
|
}
|
|
1027
1320
|
}
|
|
1028
1321
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1322
|
+
// Callable-here first, then score, then name. A tool the caller cannot
|
|
1323
|
+
// run keeps a row (it exists — the agent should not conclude the
|
|
1324
|
+
// capability is missing) but never leads. The cap holds at least
|
|
1325
|
+
// FLAGGED_MATCH_ROWS tail rows for those, and lets them fill any room
|
|
1326
|
+
// the visible rows leave, so the total never shrinks because of a scope.
|
|
1327
|
+
const ranked = [...best.values()].sort((a, b) => b.score - a.score || a.tool.localeCompare(b.tool));
|
|
1328
|
+
const visible = ranked.filter((m) => m.visible);
|
|
1329
|
+
const flagged = ranked.filter((m) => !m.visible);
|
|
1330
|
+
const flaggedShown = Math.min(flagged.length, Math.max(FLAGGED_MATCH_ROWS, DISCOVER_MATCH_LIMIT - visible.length));
|
|
1331
|
+
return [
|
|
1332
|
+
...visible.slice(0, DISCOVER_MATCH_LIMIT - flaggedShown),
|
|
1333
|
+
...flagged.slice(0, flaggedShown),
|
|
1334
|
+
];
|
|
1032
1335
|
}
|
|
1033
1336
|
/**
|
|
1034
1337
|
* A single-line description for a match row: the first sentence of the
|
|
@@ -1161,6 +1464,10 @@ export class DiscoveryTools {
|
|
|
1161
1464
|
// Collect all recovery hints for this tool (all error codes)
|
|
1162
1465
|
const hints = RECOVERY_HINTS[toolName] ?? [];
|
|
1163
1466
|
const definition = registered?.definition ?? null;
|
|
1467
|
+
// A deprecated alias is registered as a clone of its canonical, so the
|
|
1468
|
+
// schema/description below ARE the canonical's — the agent gets a working
|
|
1469
|
+
// tool plus the name it should migrate to, not a bare refusal.
|
|
1470
|
+
const deprecation = getAliasFor(toolName);
|
|
1164
1471
|
// Gate visibility (audit P1 #6): surface the authority tier + required
|
|
1165
1472
|
// scope so an agent knows BEFORE calling whether it holds the grant, and
|
|
1166
1473
|
// an explicit note when the scope is unreachable for a standard agent
|
|
@@ -1188,14 +1495,48 @@ export class DiscoveryTools {
|
|
|
1188
1495
|
// concluded "I can see the tool but can't execute it" and given up
|
|
1189
1496
|
// (observed on ChatGPT, 2026-07-08). Spell out the execution path so
|
|
1190
1497
|
// the next step is unavoidable.
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1498
|
+
// When the caller cannot run the tool here, the line above would be
|
|
1499
|
+
// false in the worst direction — pica_execute refuses it (the route's
|
|
1500
|
+
// allowedToolNames gate) — so the invoke text says so instead.
|
|
1501
|
+
invoke: this.isVisible(toolName)
|
|
1502
|
+
? `if ${toolName} is not in your callable function list, run it via the pica_execute tool: ` +
|
|
1503
|
+
`{"tool": "${toolName}", "args": { ...arguments per the schema below }}. ` +
|
|
1504
|
+
"Never tell the user the tool is unavailable — pica_execute executes every tool returned here."
|
|
1505
|
+
: `pica_execute will refuse ${toolName} on this connection (see surface_note). ` +
|
|
1506
|
+
"Do not call it here; tell the user plainly what it needs and offer a tool without this flag.",
|
|
1194
1507
|
schema: definition?.inputSchema ?? null,
|
|
1195
|
-
|
|
1508
|
+
...(definition?.outputSchema
|
|
1509
|
+
? { outputSchema: definition.outputSchema }
|
|
1510
|
+
: {}),
|
|
1511
|
+
// Derived, never the raw declaration: most tools declare no
|
|
1512
|
+
// `annotations` at all, so reading the declaration answered `null` for
|
|
1513
|
+
// the large majority of registered tools while listTools() answered real
|
|
1514
|
+
// hints for the same tool (measured 2026-08-24). deriveAnnotations() is
|
|
1515
|
+
// the one derivation both surfaces read — see index.ts and
|
|
1516
|
+
// src/__tests__/tools/tool-details-contract.test.ts.
|
|
1517
|
+
annotations: definition ? deriveAnnotations(definition) : null,
|
|
1518
|
+
// Every category key this tool is listed under. Cross-listed tools
|
|
1519
|
+
// (ADR-226 Decision 12) appear in more than one, and an agent that
|
|
1520
|
+
// arrived via one category otherwise cannot see the others.
|
|
1521
|
+
discovery_categories: Object.entries(CATEGORIES)
|
|
1522
|
+
.filter(([, cat]) => cat.tools.includes(toolName))
|
|
1523
|
+
.map(([key]) => key),
|
|
1524
|
+
...(deprecation
|
|
1525
|
+
? {
|
|
1526
|
+
deprecated: {
|
|
1527
|
+
use: deprecation.canonical,
|
|
1528
|
+
removal_target: deprecation.removal_target,
|
|
1529
|
+
},
|
|
1530
|
+
}
|
|
1531
|
+
: {}),
|
|
1196
1532
|
tier,
|
|
1197
1533
|
required_scope: requiredScope,
|
|
1534
|
+
// A scope / destructive-tier note stays whatever the surface says:
|
|
1535
|
+
// its remediation (admin elevation, confirmation token) is the part an
|
|
1536
|
+
// agent can act on. The surface flag rides beside it under its own
|
|
1537
|
+
// key (`surface_note`), never in its place.
|
|
1198
1538
|
...(callableNote ? { callable_note: callableNote } : {}),
|
|
1539
|
+
...this.surfaceFlags(toolName),
|
|
1199
1540
|
metadata: metadata
|
|
1200
1541
|
? {
|
|
1201
1542
|
category: metadata.category,
|
|
@@ -1264,7 +1605,13 @@ export class DiscoveryTools {
|
|
|
1264
1605
|
readOnlyHint: false,
|
|
1265
1606
|
destructiveHint: false,
|
|
1266
1607
|
idempotentHint: false,
|
|
1267
|
-
openWorldHint
|
|
1608
|
+
// TRUE, matching the `annotationOverrides.openWorldHint` above.
|
|
1609
|
+
// This read `false` until 2026-08-24 while the override next to it
|
|
1610
|
+
// said `true` — the override wins, so the declaration was never
|
|
1611
|
+
// published and only misled the next reader of this file. The
|
|
1612
|
+
// dispatcher wraps every tool, including the ones that email an
|
|
1613
|
+
// external recipient, so `true` is the honest value.
|
|
1614
|
+
openWorldHint: true,
|
|
1268
1615
|
},
|
|
1269
1616
|
},
|
|
1270
1617
|
executor: async (args, ctx) => {
|
|
@@ -1337,17 +1684,41 @@ export class DiscoveryTools {
|
|
|
1337
1684
|
// hallucinated variations instead of consulting the catalog.
|
|
1338
1685
|
// structuredContent mirrors the envelope so telemetry and
|
|
1339
1686
|
// structured-content-aware clients get the code without parsing text.
|
|
1687
|
+
//
|
|
1688
|
+
// The code is TOOL_NOT_FOUND, matching pica_tool_details' answer for
|
|
1689
|
+
// the same question and the ADR-149 Phase 5bcd design. It read
|
|
1690
|
+
// UNKNOWN_TOOL from 2026-07-03 until 2026-08-24; `suggestions` is kept
|
|
1691
|
+
// beside the new `did_you_mean` for callers written against that
|
|
1692
|
+
// shape. Both are asserted in
|
|
1693
|
+
// `src/__tests__/tools/discovery.test.ts`.
|
|
1694
|
+
//
|
|
1695
|
+
// `did_you_mean` is ALWAYS present, empty array included: a key that
|
|
1696
|
+
// only sometimes exists forces every caller to branch on `undefined`
|
|
1697
|
+
// before it can branch on "no suggestions".
|
|
1698
|
+
//
|
|
1699
|
+
// `retry_safe` and `retry_eligible` carry the same fact under two
|
|
1700
|
+
// names, deliberately. `retry_eligible` is the ADR-180 envelope field
|
|
1701
|
+
// that the five sibling envelopes in this file all emit, so dropping
|
|
1702
|
+
// it here alone would make one dispatcher error shaped unlike the
|
|
1703
|
+
// rest; `retry_safe` is the name the same fact wears in
|
|
1704
|
+
// `pica_tool_details.metadata.retry_safe`, which is where an agent
|
|
1705
|
+
// reads it everywhere else. Unifying the file on one key is a
|
|
1706
|
+
// separate change — do it for all six envelopes or none.
|
|
1340
1707
|
if (!this.getToolDef(toolName)) {
|
|
1341
1708
|
const suggestions = this.suggestToolNames(toolName);
|
|
1342
1709
|
const envelope = {
|
|
1343
|
-
|
|
1710
|
+
error: "TOOL_NOT_FOUND",
|
|
1711
|
+
error_code: "TOOL_NOT_FOUND",
|
|
1344
1712
|
tool: toolName,
|
|
1345
1713
|
message: `${toolName} is not a registered tool.` +
|
|
1346
1714
|
(suggestions.length > 0
|
|
1347
1715
|
? ` Closest real tools: ${suggestions.join(", ")}.`
|
|
1348
1716
|
: "") +
|
|
1349
1717
|
" Call pica_tool_details on the intended tool for its exact name and schema, or pica_discover to browse categories — do not guess tool names or argument shapes.",
|
|
1350
|
-
|
|
1718
|
+
did_you_mean: suggestions,
|
|
1719
|
+
suggestions,
|
|
1720
|
+
next_tool: "pica_discover",
|
|
1721
|
+
retry_safe: false,
|
|
1351
1722
|
retry_eligible: false,
|
|
1352
1723
|
};
|
|
1353
1724
|
return {
|
|
@@ -1355,9 +1726,14 @@ export class DiscoveryTools {
|
|
|
1355
1726
|
{ type: "text", text: JSON.stringify(envelope, null, 2) },
|
|
1356
1727
|
],
|
|
1357
1728
|
structuredContent: {
|
|
1729
|
+
error: envelope.error,
|
|
1358
1730
|
error_code: envelope.error_code,
|
|
1359
1731
|
error_message: envelope.message,
|
|
1360
|
-
|
|
1732
|
+
tool: toolName,
|
|
1733
|
+
did_you_mean: suggestions,
|
|
1734
|
+
suggestions,
|
|
1735
|
+
next_tool: "pica_discover",
|
|
1736
|
+
retry_safe: false,
|
|
1361
1737
|
},
|
|
1362
1738
|
isError: true,
|
|
1363
1739
|
};
|