@reclaimprotocol/agent 0.1.17 → 0.1.18
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/lib/consts.d.ts +7 -0
- package/lib/consts.js +15 -0
- package/lib/mcp/tools/agent/publish.js +33 -0
- package/lib/mcp/tools/mode/mode.d.ts +3 -2
- package/lib/mcp/tools/mode/mode.js +24 -4
- package/lib/old/to-register.d.ts +16 -8
- package/lib/old/to-register.js +29 -21
- package/lib/old/tools/publish.js +86 -8
- package/lib/provider/injection-doc.js +4 -3
- package/lib/provider/schema.d.ts +26 -1
- package/lib/provider/to-version.d.ts +19 -2
- package/lib/provider/to-version.js +18 -4
- package/package.json +2 -2
- package/skill/builder-mode.md +2 -0
- package/skill/old-mode.md +3 -1
- package/src/consts.ts +20 -0
- package/src/mcp/tools/agent/publish.ts +42 -0
- package/src/mcp/tools/mode/mode.ts +28 -4
- package/src/old/to-register.ts +50 -27
- package/src/old/tools/publish.ts +108 -8
- package/src/provider/injection-doc.ts +4 -3
- package/src/provider/schema.ts +27 -1
- package/src/provider/to-version.ts +41 -6
package/lib/consts.d.ts
CHANGED
|
@@ -44,6 +44,13 @@ export declare function useOldDevtools(): boolean;
|
|
|
44
44
|
* `ConfigStore` (which would create an import cycle).
|
|
45
45
|
*/
|
|
46
46
|
export declare function resolveOldMode(persisted?: 'old' | 'builder'): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Where the effective mode came from — the same precedence as
|
|
49
|
+
* `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
|
|
50
|
+
* `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
|
|
51
|
+
* user can tell why the server is in the mode it reports.
|
|
52
|
+
*/
|
|
53
|
+
export declare function oldModeSource(persisted?: 'old' | 'builder'): 'env' | 'config' | 'default';
|
|
47
54
|
/** `%LOCALAPPDATA%` on Windows, used to probe a per-user Chrome install. */
|
|
48
55
|
export declare function localAppData(): string | undefined;
|
|
49
56
|
/** Explicit Chrome/Edge/Chromium binary path override. */
|
package/lib/consts.js
CHANGED
|
@@ -76,6 +76,21 @@ export function resolveOldMode(persisted) {
|
|
|
76
76
|
}
|
|
77
77
|
return true;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Where the effective mode came from — the same precedence as
|
|
81
|
+
* `resolveOldMode`: explicit `USE_OLD_DEVTOOLS` env var → persisted config
|
|
82
|
+
* `mode` → default (old). Surfaced by the `get_devtools_mode` tool so the
|
|
83
|
+
* user can tell why the server is in the mode it reports.
|
|
84
|
+
*/
|
|
85
|
+
export function oldModeSource(persisted) {
|
|
86
|
+
if (oldDevtoolsEnvSet()) {
|
|
87
|
+
return 'env';
|
|
88
|
+
}
|
|
89
|
+
if (persisted) {
|
|
90
|
+
return 'config';
|
|
91
|
+
}
|
|
92
|
+
return 'default';
|
|
93
|
+
}
|
|
79
94
|
// ---------------------------------------------------------------------------
|
|
80
95
|
// Chrome / CDP launcher.
|
|
81
96
|
// ---------------------------------------------------------------------------
|
|
@@ -13,6 +13,11 @@ const RESPONSE_MATCHES_SCHEMA = {
|
|
|
13
13
|
properties: {
|
|
14
14
|
type: { type: 'string', enum: ['regex', 'contains'] },
|
|
15
15
|
value: { type: 'string' },
|
|
16
|
+
isOptional: {
|
|
17
|
+
type: 'boolean',
|
|
18
|
+
description: 'Mark this match non-fatal (verification continues when it ' +
|
|
19
|
+
'fails). Default false.',
|
|
20
|
+
},
|
|
16
21
|
},
|
|
17
22
|
required: ['type', 'value'],
|
|
18
23
|
},
|
|
@@ -44,6 +49,12 @@ const REQUEST_DRAFT_PROPERTIES = {
|
|
|
44
49
|
},
|
|
45
50
|
method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH'] },
|
|
46
51
|
body: { type: 'string', description: 'Request body template, if any.' },
|
|
52
|
+
credentials: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
enum: ['omit', 'same-origin', 'include'],
|
|
55
|
+
description: 'How the verification client supplies cookies/auth when replaying ' +
|
|
56
|
+
'this request. Default `include`.',
|
|
57
|
+
},
|
|
47
58
|
responseMatches: RESPONSE_MATCHES_SCHEMA,
|
|
48
59
|
responseRedactions: RESPONSE_REDACTIONS_SCHEMA,
|
|
49
60
|
};
|
|
@@ -173,6 +184,27 @@ export function createVersionFromCaptureTool(client) {
|
|
|
173
184
|
'`multiple`/`required` default to true.',
|
|
174
185
|
items: ALLOWED_JS_REQUEST_SCHEMA,
|
|
175
186
|
},
|
|
187
|
+
interceptorOptions: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
description: 'In-app interception settings, stored as ' +
|
|
190
|
+
'webSettings.clientOptions.inapp.interceptorOptions. ' +
|
|
191
|
+
'`isDocumentRequestReplayEnabled` is the INVERSE of ' +
|
|
192
|
+
'old-devtools\' `disableRequestReplay`: true replays the ' +
|
|
193
|
+
'main document request via the attestor (default false).',
|
|
194
|
+
properties: {
|
|
195
|
+
interceptorType: {
|
|
196
|
+
type: 'string',
|
|
197
|
+
enum: ['HAWKEYE', 'MSWJS', 'CDP'],
|
|
198
|
+
description: 'How traffic interception is implemented.',
|
|
199
|
+
},
|
|
200
|
+
isDocumentRequestReplayEnabled: { type: 'boolean' },
|
|
201
|
+
interceptorSettings: {
|
|
202
|
+
type: 'string',
|
|
203
|
+
description: 'Free-form JSON config for the chosen interceptor.',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
required: ['interceptorType'],
|
|
207
|
+
},
|
|
176
208
|
},
|
|
177
209
|
required: ['providerId', 'initialUrl'],
|
|
178
210
|
},
|
|
@@ -192,6 +224,7 @@ export function createVersionFromCaptureTool(client) {
|
|
|
192
224
|
version: ver,
|
|
193
225
|
jsUserScripts: args.jsUserScripts,
|
|
194
226
|
allowedJsRequests: args.allowedJsRequests,
|
|
227
|
+
interceptorOptions: args.interceptorOptions,
|
|
195
228
|
});
|
|
196
229
|
const result = await client.call('CreateProviderVersion', {
|
|
197
230
|
params: { providerId: args.providerId },
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ConfigStore } from '../../../config/store.ts';
|
|
2
2
|
import { type RegisteredTool } from '../../server.ts';
|
|
3
3
|
/**
|
|
4
|
-
* The `set_devtools_mode`
|
|
5
|
-
* (
|
|
4
|
+
* The `set_devtools_mode` / `get_devtools_mode` tools: persist which backend
|
|
5
|
+
* the MCP server targets (`old` devtools vs the `builder`), and
|
|
6
|
+
* report the mode currently in effect. The choice is written to
|
|
6
7
|
* `~/.reclaim/config.json` and applied the NEXT time the server starts —
|
|
7
8
|
* the registered tool set, server instructions, and backend client are all
|
|
8
9
|
* fixed at connect time, so the switch requires a restart/reconnect. Follows
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { defaultConfigPath } from "../../../config/store.js";
|
|
2
|
+
import { oldModeSource } from "../../../consts.js";
|
|
2
3
|
import { defineTool } from "../../server.js";
|
|
3
4
|
/**
|
|
4
|
-
* The `set_devtools_mode`
|
|
5
|
-
* (
|
|
5
|
+
* The `set_devtools_mode` / `get_devtools_mode` tools: persist which backend
|
|
6
|
+
* the MCP server targets (`old` devtools vs the `builder`), and
|
|
7
|
+
* report the mode currently in effect. The choice is written to
|
|
6
8
|
* `~/.reclaim/config.json` and applied the NEXT time the server starts —
|
|
7
9
|
* the registered tool set, server instructions, and backend client are all
|
|
8
10
|
* fixed at connect time, so the switch requires a restart/reconnect. Follows
|
|
@@ -18,8 +20,8 @@ export function buildModeTools(config, currentOldMode) {
|
|
|
18
20
|
return [
|
|
19
21
|
defineTool({
|
|
20
22
|
name: 'set_devtools_mode',
|
|
21
|
-
description: 'Choose which backend the MCP server targets: "old" (
|
|
22
|
-
+ '
|
|
23
|
+
description: 'Choose which backend the MCP server targets: "old" (devtools) '
|
|
24
|
+
+ 'or "builder" (the builder backend). '
|
|
23
25
|
+ 'Persists the choice to ~/.reclaim/config.json; it takes effect '
|
|
24
26
|
+ 'the next time the MCP server starts, so restart/reconnect after '
|
|
25
27
|
+ 'switching. An explicit USE_OLD_DEVTOOLS env var overrides this.',
|
|
@@ -45,5 +47,23 @@ export function buildModeTools(config, currentOldMode) {
|
|
|
45
47
|
: `Already running in "${parsed.mode}" mode; nothing to restart.`,
|
|
46
48
|
};
|
|
47
49
|
}),
|
|
50
|
+
defineTool({
|
|
51
|
+
name: 'get_devtools_mode',
|
|
52
|
+
description: 'Report which backend this MCP server is currently targeting: '
|
|
53
|
+
+ '"old" (devtools) or "builder" (the builder '
|
|
54
|
+
+ 'backend). Also reports WHY that mode is in effect (source: '
|
|
55
|
+
+ 'a USE_OLD_DEVTOOLS env override, the persisted config, or '
|
|
56
|
+
+ 'the default) and which mode is persisted for the next '
|
|
57
|
+
+ 'start. Use set_devtools_mode to change it.',
|
|
58
|
+
inputSchema: { type: 'object', properties: {} },
|
|
59
|
+
}, async () => {
|
|
60
|
+
const persisted = config.read()?.mode;
|
|
61
|
+
return {
|
|
62
|
+
mode: current,
|
|
63
|
+
source: oldModeSource(persisted),
|
|
64
|
+
persistedMode: persisted ?? null,
|
|
65
|
+
configPath: defaultConfigPath(),
|
|
66
|
+
};
|
|
67
|
+
}),
|
|
48
68
|
];
|
|
49
69
|
}
|
package/lib/old/to-register.d.ts
CHANGED
|
@@ -3,15 +3,15 @@ import type { AllowedJsRequest } from '../provider/to-version.ts';
|
|
|
3
3
|
import type { RegisterProviderBody, UpdateProviderConfigBody } from './client.ts';
|
|
4
4
|
/**
|
|
5
5
|
* Translate a drafted `ReclaimProvider` (from `propose_provider`) into the
|
|
6
|
-
*
|
|
6
|
+
* devtools backend shapes. Deliberately a SEPARATE module from
|
|
7
7
|
* `provider/to-version.ts` (the builder translator) — the two backends have
|
|
8
8
|
* structurally different bodies:
|
|
9
9
|
*
|
|
10
10
|
* - builder NESTS each redaction into its match's `extract`;
|
|
11
|
-
* -
|
|
11
|
+
* - devtools keeps `responseMatches` and `responseRedactions` as
|
|
12
12
|
* PARALLEL arrays on each `requestData` entry.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Devtools limit we enforce here (a clear error beats a confusing
|
|
15
15
|
* server-side 400): only `GET` / `POST` methods. OPRF redactions are passed
|
|
16
16
|
* through unchanged — the backend's `hash` field accepts them.
|
|
17
17
|
*/
|
|
@@ -23,7 +23,7 @@ export interface OldProviderOpts {
|
|
|
23
23
|
/** Defaults to `true` when omitted. Register only (Provider-level flag). */
|
|
24
24
|
useProxy?: boolean;
|
|
25
25
|
providerType?: 'PRIVATE' | 'PUBLIC';
|
|
26
|
-
/** Page-injection script —
|
|
26
|
+
/** Page-injection script — the devtools name for the builder's
|
|
27
27
|
* `webSettings.jsUserScripts`. Top-level on register, nested under
|
|
28
28
|
* `providerConfig` on config (add-version). The backend rejects a few
|
|
29
29
|
* deprecated injection APIs (e.g. `window.payloadData`) with a 400. */
|
|
@@ -50,6 +50,14 @@ export interface OldProviderOpts {
|
|
|
50
50
|
/** Config (add-version) ONLY — the register endpoint never reads this
|
|
51
51
|
* field from the request body. Free-form JSON. */
|
|
52
52
|
extensionConfig?: unknown;
|
|
53
|
+
/** Provider-level `disableRequestReplay` (HAWKEYE document replay skip).
|
|
54
|
+
* Config (add-version) ONLY: the register endpoint hardcodes it to false
|
|
55
|
+
* — the publish tool follows a register up with an add-version call to
|
|
56
|
+
* apply it for new providers (see `old/tools/publish.ts`). NOTE the
|
|
57
|
+
* builder backend has NO equivalent; its inverse is
|
|
58
|
+
* `webSettings.clientOptions.inapp.interceptorOptions
|
|
59
|
+
* .isDocumentRequestReplayEnabled`. */
|
|
60
|
+
disableRequestReplay?: boolean;
|
|
53
61
|
}
|
|
54
62
|
interface OldResponseMatch {
|
|
55
63
|
value: string;
|
|
@@ -68,11 +76,11 @@ interface OldResponseRedaction {
|
|
|
68
76
|
}
|
|
69
77
|
interface OldRequestData {
|
|
70
78
|
url: string;
|
|
71
|
-
urlType: 'CONSTANT' | 'TEMPLATE';
|
|
79
|
+
urlType: 'REGEX' | 'CONSTANT' | 'TEMPLATE';
|
|
72
80
|
method: 'GET' | 'POST';
|
|
73
81
|
responseMatches: OldResponseMatch[];
|
|
74
82
|
responseRedactions: OldResponseRedaction[];
|
|
75
|
-
credentials: 'include';
|
|
83
|
+
credentials: 'omit' | 'same-origin' | 'include';
|
|
76
84
|
bodySniff?: {
|
|
77
85
|
enabled: boolean;
|
|
78
86
|
template: string;
|
|
@@ -88,7 +96,7 @@ export interface OldInjectedRequestData extends OldRequestData {
|
|
|
88
96
|
}
|
|
89
97
|
/** Translate fresh `allowedInjectedRequestData` drafts (the author's input
|
|
90
98
|
* shape, `AllowedJsRequest[]` — same shape the builder translator uses for
|
|
91
|
-
* its own `allowedJsRequests`) into the
|
|
99
|
+
* its own `allowedJsRequests`) into the devtools backend's shape. A
|
|
92
100
|
* carried-forward value read back from an existing version is ALREADY in
|
|
93
101
|
* this shape and should be passed straight through instead — don't run it
|
|
94
102
|
* through this a second time. */
|
|
@@ -101,7 +109,7 @@ export declare function providerToRegisterBody(providers: ReclaimProvider[], opt
|
|
|
101
109
|
* an EXISTING provider. `version` must not already exist; `versionInfo` is
|
|
102
110
|
* required and must be ≥10 chars (enforced by the backend). */
|
|
103
111
|
export declare function providerToConfigBody(providers: ReclaimProvider[], opts: OldProviderOpts, version: string, versionInfo: string): UpdateProviderConfigBody;
|
|
104
|
-
/** Minimal version triplet as the
|
|
112
|
+
/** Minimal version triplet as the devtools backend stores it. */
|
|
105
113
|
export interface SemverTriplet {
|
|
106
114
|
major: number;
|
|
107
115
|
minor: number;
|
package/lib/old/to-register.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
1
2
|
/** Default web settings, mirroring the builder openapi defaults
|
|
2
|
-
* (`ProviderWebSettings.geoLocation`, `PortalClientOptions.useProxy`). The
|
|
3
|
+
* (`ProviderWebSettings.geoLocation`, `PortalClientOptions.useProxy`). The
|
|
3
4
|
* backend accepts both: `geoLocation` lives on the providerConfig, `useProxy`
|
|
4
5
|
* is a Provider-level flag (register only). */
|
|
5
6
|
const DEFAULT_GEO_LOCATION = '{{DYNAMIC_GEO}}';
|
|
@@ -10,29 +11,29 @@ const DEFAULT_USE_PROXY = true;
|
|
|
10
11
|
* entries — a `ReclaimProvider` satisfies it structurally either way. */
|
|
11
12
|
function requestDataFromProvider(provider) {
|
|
12
13
|
const method = String(provider.method).toUpperCase();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
// The old backend has no dotted interpolation: consumer-supplied params
|
|
14
|
+
assert(method === 'GET' || method === 'POST', new Error('Devtools supports only GET/POST requests; got ' +
|
|
15
|
+
`"${provider.method}". Re-capture against a GET/POST endpoint.`));
|
|
16
|
+
// The devtools backend has no dotted interpolation: consumer-supplied params
|
|
18
17
|
// must use the `{{context_<name>}}` form here, not the builder's
|
|
19
18
|
// `{{context.<name>}}`. Catch the mix-up at publish with a clear error
|
|
20
19
|
// instead of a template that silently never resolves.
|
|
21
20
|
const dotted = `${provider.url}\n${provider.body ?? ''}`
|
|
22
21
|
.match(/\{\{context\.[A-Za-z_][A-Za-z0-9_]*\}\}/);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'for this backend.');
|
|
27
|
-
}
|
|
22
|
+
assert(!dotted, new Error(`Devtools does not support ${dotted?.[0]} — rename ` +
|
|
23
|
+
'consumer-supplied params to the {{context_<name>}} form ' +
|
|
24
|
+
'for this backend.'));
|
|
28
25
|
const responseMatches = (provider.responseMatches ?? [])
|
|
29
26
|
.map((m, i) => ({
|
|
30
27
|
value: m.value,
|
|
31
28
|
type: m.type ?? 'contains',
|
|
32
|
-
isOptional
|
|
29
|
+
// The backend honors isOptional on BOTH register and add-version
|
|
30
|
+
// (responseMatches pass through verbatim) — forward the author's
|
|
31
|
+
// flag, default false. (`invert` has no old-backend equivalent and
|
|
32
|
+
// stays dropped.)
|
|
33
|
+
isOptional: m.isOptional ?? false,
|
|
33
34
|
order: i,
|
|
34
35
|
}));
|
|
35
|
-
//
|
|
36
|
+
// Devtools wants `responseMatches` and `responseRedactions` as PARALLEL
|
|
36
37
|
// arrays — same shape the builder uses, so pass them through directly. We do
|
|
37
38
|
// NOT enforce any match/redaction count relationship here (and never pad —
|
|
38
39
|
// empty placeholder matches break proof generation): the author keeps full
|
|
@@ -58,33 +59,35 @@ function requestDataFromProvider(provider) {
|
|
|
58
59
|
});
|
|
59
60
|
const requestData = {
|
|
60
61
|
url: provider.url,
|
|
61
|
-
// `{{param}}` placeholders → TEMPLATE
|
|
62
|
-
//
|
|
63
|
-
urlType: provider.
|
|
62
|
+
// Explicit override wins; otherwise `{{param}}` placeholders → TEMPLATE
|
|
63
|
+
// so the backend templates them, else a fixed CONSTANT url.
|
|
64
|
+
urlType: provider.urlType
|
|
65
|
+
?? (provider.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT'),
|
|
64
66
|
method: method,
|
|
65
67
|
responseMatches,
|
|
66
68
|
responseRedactions,
|
|
67
69
|
// The verification client re-supplies cookies/auth at runtime; secrets
|
|
68
70
|
// are never persisted (propose_provider already stripped them).
|
|
69
|
-
credentials: 'include',
|
|
71
|
+
credentials: provider.credentials ?? 'include',
|
|
70
72
|
};
|
|
71
73
|
if (provider.body !== undefined) {
|
|
72
74
|
requestData.bodySniff = { enabled: true, template: provider.body };
|
|
73
75
|
}
|
|
74
|
-
// NOTE: `responseVariables` is intentionally omitted — the
|
|
76
|
+
// NOTE: `responseVariables` is intentionally omitted — the devtools backend
|
|
75
77
|
// auto-extracts it from `{{var}}` placeholders in responseMatches. Sending
|
|
76
78
|
// it ourselves would risk drifting from the server's own extraction.
|
|
77
79
|
return requestData;
|
|
78
80
|
}
|
|
79
81
|
/** One `requestData` entry per drafted provider. A multi-request provider
|
|
80
|
-
* becomes parallel entries that the
|
|
81
|
-
* (the same way the builder version carries multiple
|
|
82
|
+
* becomes parallel entries that the devtools backend runs together at
|
|
83
|
+
* verification (the same way the builder version carries multiple
|
|
84
|
+
* `requests`). */
|
|
82
85
|
function requestDataFrom(providers) {
|
|
83
86
|
return providers.map(requestDataFromProvider);
|
|
84
87
|
}
|
|
85
88
|
/** Translate fresh `allowedInjectedRequestData` drafts (the author's input
|
|
86
89
|
* shape, `AllowedJsRequest[]` — same shape the builder translator uses for
|
|
87
|
-
* its own `allowedJsRequests`) into the
|
|
90
|
+
* its own `allowedJsRequests`) into the devtools backend's shape. A
|
|
88
91
|
* carried-forward value read back from an existing version is ALREADY in
|
|
89
92
|
* this shape and should be passed straight through instead — don't run it
|
|
90
93
|
* through this a second time. */
|
|
@@ -165,6 +168,11 @@ export function providerToConfigBody(providers, opts, version, versionInfo) {
|
|
|
165
168
|
if (opts.extensionConfig !== undefined) {
|
|
166
169
|
providerConfig.extensionConfig = opts.extensionConfig;
|
|
167
170
|
}
|
|
171
|
+
// The config (add-version) controller persists providerConfig verbatim,
|
|
172
|
+
// so this lands as-is; the register controller ignores it entirely.
|
|
173
|
+
if (opts.disableRequestReplay !== undefined) {
|
|
174
|
+
providerConfig.disableRequestReplay = opts.disableRequestReplay;
|
|
175
|
+
}
|
|
168
176
|
return { providerConfig, version, versionInfo };
|
|
169
177
|
}
|
|
170
178
|
/**
|
package/lib/old/tools/publish.js
CHANGED
|
@@ -4,6 +4,12 @@ import { INJECTION_GUIDE } from "../../provider/injection-doc.js";
|
|
|
4
4
|
import { countsFor, matchRedactionWarnings, } from "../../provider/match-redaction-warning.js";
|
|
5
5
|
import { bumpPatchString, extractVersions, injectedRequestDataFrom, providerToConfigBody, providerToRegisterBody, } from "../to-register.js";
|
|
6
6
|
const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture';
|
|
7
|
+
/** Pull the new provider's id out of a register response, tolerant of the
|
|
8
|
+
* envelope (`{providerId}`, `{provider:{providerId}}`, `{id}`). */
|
|
9
|
+
function extractProviderId(resp) {
|
|
10
|
+
const obj = resp;
|
|
11
|
+
return obj?.providerId ?? obj?.provider?.providerId ?? obj?.id;
|
|
12
|
+
}
|
|
7
13
|
/**
|
|
8
14
|
* The old backend lets you add a version only to a provider YOU own. A
|
|
9
15
|
* `providerId` for a public provider (or anyone else's) comes back as
|
|
@@ -44,7 +50,7 @@ function rethrowOwnershipError(err, providerId) {
|
|
|
44
50
|
export function createVersionFromCaptureTool(client) {
|
|
45
51
|
return defineTool({
|
|
46
52
|
name: 'create_provider_version_from_capture',
|
|
47
|
-
description: 'Publish drafted ReclaimProvider(s) to the
|
|
53
|
+
description: 'Publish drafted ReclaimProvider(s) to the devtools backend. ' +
|
|
48
54
|
'Pass the `provider` object from propose_provider VERBATIM and the ' +
|
|
49
55
|
'`initialUrl` the user landed on (the login/landing page, NOT the ' +
|
|
50
56
|
'API url). \n\n' +
|
|
@@ -62,7 +68,7 @@ export function createVersionFromCaptureTool(client) {
|
|
|
62
68
|
"PUBLIC provider you don't own, omit `providerId` to register your " +
|
|
63
69
|
'own copy (you cannot edit someone else\'s provider here). \n\n' +
|
|
64
70
|
'For data gated behind login/navigation, pass `customInjection` — a ' +
|
|
65
|
-
'page-injection script (
|
|
71
|
+
'page-injection script (the devtools name for the builder\'s ' +
|
|
66
72
|
'jsUserScripts) that drives the page to where the request fires. \n\n' +
|
|
67
73
|
'BY DESIGN, this backend only ever CREATES A NEW VERSION (POST) — ' +
|
|
68
74
|
'there is no partial-patch/edit endpoint, and this tool does NOT ' +
|
|
@@ -79,7 +85,14 @@ export function createVersionFromCaptureTool(client) {
|
|
|
79
85
|
'values plus your change) back into this call. ' +
|
|
80
86
|
'`allowedInjectedRequestData`/`useIncognitoWebview`/' +
|
|
81
87
|
'`extensionConfig` only take effect on add-version — the register ' +
|
|
82
|
-
'endpoint (no `providerId`) ignores them.
|
|
88
|
+
'endpoint (no `providerId`) ignores them. `disableRequestReplay` ' +
|
|
89
|
+
'is also add-version-only, but when it is true the tool follows ' +
|
|
90
|
+
'a register up with an add-version call automatically so it takes ' +
|
|
91
|
+
'effect on new providers too. Per-request fields you may set on ' +
|
|
92
|
+
'`provider`/`providers`: `credentials` (omit/same-origin/include, ' +
|
|
93
|
+
'default include), `urlType` (REGEX/CONSTANT/TEMPLATE, default ' +
|
|
94
|
+
'derived from {{placeholders}}), and `isOptional` on each ' +
|
|
95
|
+
'responseMatch (default false). \n\n' +
|
|
83
96
|
'Captured requests must be GET/POST. OPRF redactions are supported.',
|
|
84
97
|
inputSchema: {
|
|
85
98
|
type: 'object',
|
|
@@ -94,6 +107,19 @@ export function createVersionFromCaptureTool(client) {
|
|
|
94
107
|
method: { type: 'string' },
|
|
95
108
|
headers: { type: 'object' },
|
|
96
109
|
body: { type: 'string' },
|
|
110
|
+
credentials: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
enum: ['omit', 'same-origin', 'include'],
|
|
113
|
+
description: 'How the verification client supplies cookies/auth ' +
|
|
114
|
+
'when replaying this request. Default `include`.',
|
|
115
|
+
},
|
|
116
|
+
urlType: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
|
|
119
|
+
description: 'How the backend treats the URL. Default derived: ' +
|
|
120
|
+
'TEMPLATE when the URL has {{placeholders}}, else ' +
|
|
121
|
+
'CONSTANT.',
|
|
122
|
+
},
|
|
97
123
|
responseMatches: { type: 'array' },
|
|
98
124
|
responseRedactions: { type: 'array' },
|
|
99
125
|
paramValues: { type: 'object' },
|
|
@@ -122,6 +148,14 @@ export function createVersionFromCaptureTool(client) {
|
|
|
122
148
|
method: { type: 'string' },
|
|
123
149
|
headers: { type: 'object' },
|
|
124
150
|
body: { type: 'string' },
|
|
151
|
+
credentials: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
enum: ['omit', 'same-origin', 'include'],
|
|
154
|
+
},
|
|
155
|
+
urlType: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
|
|
158
|
+
},
|
|
125
159
|
responseMatches: { type: 'array' },
|
|
126
160
|
responseRedactions: { type: 'array' },
|
|
127
161
|
paramValues: { type: 'object' },
|
|
@@ -202,6 +236,13 @@ export function createVersionFromCaptureTool(client) {
|
|
|
202
236
|
properties: {
|
|
203
237
|
url: { type: 'string' },
|
|
204
238
|
method: { type: 'string' },
|
|
239
|
+
urlType: {
|
|
240
|
+
type: 'string',
|
|
241
|
+
enum: ['REGEX', 'CONSTANT', 'TEMPLATE'],
|
|
242
|
+
description: 'How the backend treats this URL. Default derived ' +
|
|
243
|
+
'from {{placeholders}} (TEMPLATE) like a normal ' +
|
|
244
|
+
'request entry.',
|
|
245
|
+
},
|
|
205
246
|
body: {
|
|
206
247
|
type: 'string',
|
|
207
248
|
description: 'Becomes `bodySniff: { enabled: true, template: <this ' +
|
|
@@ -315,6 +356,14 @@ export function createVersionFromCaptureTool(client) {
|
|
|
315
356
|
description: 'Free-form JSON passed through to the client. Add-version ' +
|
|
316
357
|
'ONLY.',
|
|
317
358
|
},
|
|
359
|
+
disableRequestReplay: {
|
|
360
|
+
type: 'boolean',
|
|
361
|
+
description: 'Skip replaying the main document request (HAWKEYE ' +
|
|
362
|
+
'provider-level flag). Honored on add-version; for a ' +
|
|
363
|
+
'provider (no `providerId`) the register endpoint ignores ' +
|
|
364
|
+
'it, so when true the tool automatically follows the ' +
|
|
365
|
+
'register up with an add-version call that applies it.',
|
|
366
|
+
},
|
|
318
367
|
},
|
|
319
368
|
required: ['initialUrl'],
|
|
320
369
|
},
|
|
@@ -348,6 +397,7 @@ export function createVersionFromCaptureTool(client) {
|
|
|
348
397
|
stepsToFollow: args.stepsToFollow,
|
|
349
398
|
useIncognitoWebview: args.useIncognitoWebview,
|
|
350
399
|
extensionConfig: args.extensionConfig,
|
|
400
|
+
disableRequestReplay: args.disableRequestReplay,
|
|
351
401
|
allowedInjectedRequestData: args.allowedInjectedRequestData
|
|
352
402
|
? injectedRequestDataFrom(args.allowedInjectedRequestData)
|
|
353
403
|
: undefined
|
|
@@ -372,6 +422,11 @@ export function createVersionFromCaptureTool(client) {
|
|
|
372
422
|
'every page load (before navigating to initialUrl), matching ' +
|
|
373
423
|
'the builder\'s jsUserScripts.');
|
|
374
424
|
}
|
|
425
|
+
// disableRequestReplay is honored ONLY on add-version, so a
|
|
426
|
+
// register can't set it — when true we immediately follow the
|
|
427
|
+
// register with an add-version call (which also lands any other
|
|
428
|
+
// register-ignored fields).
|
|
429
|
+
const followUp = args.disableRequestReplay === true;
|
|
375
430
|
const REGISTER_IGNORED_KEYS = [
|
|
376
431
|
'allowedInjectedRequestData',
|
|
377
432
|
'useIncognitoWebview',
|
|
@@ -382,13 +437,36 @@ export function createVersionFromCaptureTool(client) {
|
|
|
382
437
|
if (ignoredOnRegister.length) {
|
|
383
438
|
notes.push(`${ignoredOnRegister.join(', ')} ` +
|
|
384
439
|
`${ignoredOnRegister.length > 1 ? 'are' : 'is'} ` +
|
|
385
|
-
'ignored on register (no providerId) — the
|
|
386
|
-
'reads them when adding a version.
|
|
387
|
-
|
|
388
|
-
|
|
440
|
+
'ignored on register (no providerId) — the devtools backend only ' +
|
|
441
|
+
'reads them when adding a version. ' +
|
|
442
|
+
(followUp
|
|
443
|
+
? 'They were applied by the follow-up version the tool ' +
|
|
444
|
+
'created to set disableRequestReplay.'
|
|
445
|
+
: 'Publish once to create the provider, then call this ' +
|
|
446
|
+
'again WITH the returned providerId to set them.'));
|
|
389
447
|
}
|
|
390
448
|
const body = providerToRegisterBody(providers, opts);
|
|
391
|
-
|
|
449
|
+
const registered = await client.registerProvider(body);
|
|
450
|
+
if (followUp) {
|
|
451
|
+
const newProviderId = extractProviderId(registered);
|
|
452
|
+
if (newProviderId) {
|
|
453
|
+
const versionsResp = await client.listVersions(newProviderId);
|
|
454
|
+
const followVersion = bumpPatchString(extractVersions(versionsResp));
|
|
455
|
+
const configBody = providerToConfigBody(providers, { ...opts, disableRequestReplay: true }, followVersion, DEFAULT_VERSION_INFO);
|
|
456
|
+
await client.updateProviderConfig(newProviderId, configBody);
|
|
457
|
+
notes.push('disableRequestReplay is ignored by the register endpoint — ' +
|
|
458
|
+
`applied it via a follow-up version (${followVersion}) on ` +
|
|
459
|
+
'the new provider, which also lands any register-ignored ' +
|
|
460
|
+
'fields you passed.');
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
notes.push('Could not apply disableRequestReplay: the register ' +
|
|
464
|
+
'response did not include the new providerId. Call this ' +
|
|
465
|
+
'tool again WITH `providerId` and `disableRequestReplay: ' +
|
|
466
|
+
'true` to set it.');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return withNotes(registered);
|
|
392
470
|
}
|
|
393
471
|
// New version of an existing provider — pick the next patch above
|
|
394
472
|
// the highest existing version. A provider you don't own (e.g. a
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* It also covers the HAWKEYE-style live-interception path (`injectionType` /
|
|
15
15
|
* `interceptorType`, `disableRequestReplay` / `isDocumentRequestReplayEnabled`)
|
|
16
16
|
* and `bodySniff`, reverse-engineered from the old-devtools Prisma schema,
|
|
17
|
-
* controller code, and dashboard field hints
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* controller code, and dashboard field hints. The replay flag IS settable via
|
|
18
|
+
* the publish tools (`disableRequestReplay` on old-devtools — add-version or
|
|
19
|
+
* the register follow-up; `interceptorOptions` on the builder), so this is
|
|
20
|
+
* recognition + authoring guidance for the remaining internals.
|
|
20
21
|
*/
|
|
21
22
|
import { readFileSync } from 'node:fs';
|
|
22
23
|
import { fileURLToPath } from 'node:url';
|
package/lib/provider/schema.d.ts
CHANGED
|
@@ -11,6 +11,24 @@ export type HttpMethod = HttpParams['method'];
|
|
|
11
11
|
* jsonPath/xPath belong on the redaction, not here.
|
|
12
12
|
*/
|
|
13
13
|
export type ResponseMatch = HttpParams['responseMatches'][number];
|
|
14
|
+
/**
|
|
15
|
+
* A response match as drafted for PUBLISH: the attestor's match plus
|
|
16
|
+
* `isOptional` — a backend-side matcher flag (both the old devtools backend
|
|
17
|
+
* and the builder store it; the attestor's own params have no such field, so
|
|
18
|
+
* `propose_provider` drafts never set it — only an explicit publish input
|
|
19
|
+
* does). Default false on both backends.
|
|
20
|
+
*/
|
|
21
|
+
export type DraftResponseMatch = ResponseMatch & {
|
|
22
|
+
isOptional?: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** How the verification client supplies cookies/auth when replaying a
|
|
25
|
+
* request. Both backends accept the same enum (old devtools on
|
|
26
|
+
* `requestData`, builder on `RequestSelection`); default `include`. */
|
|
27
|
+
export type WebCredentials = 'omit' | 'same-origin' | 'include';
|
|
28
|
+
/** Old-devtools `urlType` for a requestData entry. The builder backend has
|
|
29
|
+
* no such field (templating is implicit in the URL's `{{param}}`
|
|
30
|
+
* placeholders), so the builder translator ignores it. */
|
|
31
|
+
export type OldUrlType = 'REGEX' | 'CONSTANT' | 'TEMPLATE';
|
|
14
32
|
/**
|
|
15
33
|
* Selector telling the prover which portion of the response to reveal:
|
|
16
34
|
* `xPath`/`jsonPath` narrows the slice, `regex` (with named groups) matches
|
|
@@ -59,8 +77,15 @@ export interface ReclaimProvider {
|
|
|
59
77
|
method: HttpMethod;
|
|
60
78
|
headers?: Record<string, string>;
|
|
61
79
|
body?: string;
|
|
62
|
-
responseMatches:
|
|
80
|
+
responseMatches: DraftResponseMatch[];
|
|
63
81
|
responseRedactions?: ResponseRedaction[];
|
|
82
|
+
/** How the verification client supplies cookies/auth on replay. Both
|
|
83
|
+
* backends default to `include` when unset. */
|
|
84
|
+
credentials?: WebCredentials;
|
|
85
|
+
/** Old-devtools-only url type override (`REGEX`/`CONSTANT`/`TEMPLATE`).
|
|
86
|
+
* Auto-derived from `{{param}}` placeholders when unset; ignored by the
|
|
87
|
+
* builder backend. */
|
|
88
|
+
urlType?: OldUrlType;
|
|
64
89
|
/** Public param refs (templated into URL/body), populated by draft layer. */
|
|
65
90
|
paramValues?: Record<string, string>;
|
|
66
91
|
/** Redaction mode for the transcript (attestor default: 'key-update'). */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client';
|
|
2
2
|
import type { CreateProviderVersionRequest, ProviderVersionRef } from '@reclaimprotocol/client/openapi';
|
|
3
|
-
import type { HttpMethod,
|
|
3
|
+
import type { DraftResponseMatch, HttpMethod, OldUrlType, ReclaimProvider, ResponseRedaction, WebCredentials } from './schema.ts';
|
|
4
4
|
/** One entry of the version's `requests[]` (a `RequestSelection`), derived
|
|
5
5
|
* from the openapi type so it can't drift from what the server accepts. */
|
|
6
6
|
type RequestSelection = CreateProviderVersionRequest['requests'][number];
|
|
@@ -13,8 +13,15 @@ export interface DraftRequest {
|
|
|
13
13
|
body?: string;
|
|
14
14
|
writeRedactionMode?: ReclaimProvider['writeRedactionMode'];
|
|
15
15
|
additionalClientOptions?: ReclaimProvider['additionalClientOptions'];
|
|
16
|
-
responseMatches?:
|
|
16
|
+
responseMatches?: DraftResponseMatch[];
|
|
17
17
|
responseRedactions?: ResponseRedaction[];
|
|
18
|
+
/** How the verification client supplies cookies/auth on replay. Default
|
|
19
|
+
* `include` (both backends). */
|
|
20
|
+
credentials?: WebCredentials;
|
|
21
|
+
/** Old-devtools `urlType` override. Ignored by THIS translator (the
|
|
22
|
+
* builder backend has no urlType) — read only by the old-devtools
|
|
23
|
+
* translator (`old/to-register.ts`). */
|
|
24
|
+
urlType?: OldUrlType;
|
|
18
25
|
}
|
|
19
26
|
/** A request the page's injection script is allowed to fire, plus the
|
|
20
27
|
* pagination/optionality flags of `RequestSelectionTemplate`. Reuses the
|
|
@@ -47,6 +54,16 @@ export interface CreateVersionOptions {
|
|
|
47
54
|
jsUserScripts?: string;
|
|
48
55
|
/** Requests the injection script may fire, as `RequestSelectionTemplate`s. */
|
|
49
56
|
allowedJsRequests?: AllowedJsRequest[];
|
|
57
|
+
/** In-app interception options, mapped verbatim to
|
|
58
|
+
* `webSettings.clientOptions.inapp.interceptorOptions`. `interceptorType`
|
|
59
|
+
* is required by the backend whenever the object is present.
|
|
60
|
+
* `isDocumentRequestReplayEnabled` is the INVERSE of old-devtools'
|
|
61
|
+
* `disableRequestReplay` (default false). */
|
|
62
|
+
interceptorOptions?: {
|
|
63
|
+
interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP';
|
|
64
|
+
isDocumentRequestReplayEnabled?: boolean;
|
|
65
|
+
interceptorSettings?: string;
|
|
66
|
+
};
|
|
50
67
|
}
|
|
51
68
|
/**
|
|
52
69
|
* Derive the version's `requiredContext` JSON Schema from the
|