@lerianstudio/matcher-mcp 1.3.0 → 1.4.0-beta.2
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/README.md +26 -0
- package/dist/auth/request-token.js +50 -2
- package/dist/auth/request-token.js.map +1 -1
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -1
- package/dist/copilot/agent.js +181 -0
- package/dist/copilot/agent.js.map +1 -0
- package/dist/copilot/provider-anthropic.js +151 -0
- package/dist/copilot/provider-anthropic.js.map +1 -0
- package/dist/copilot/provider-factory.js +46 -0
- package/dist/copilot/provider-factory.js.map +1 -0
- package/dist/copilot/provider-openrouter.js +154 -0
- package/dist/copilot/provider-openrouter.js.map +1 -0
- package/dist/copilot/provider-shared.js +23 -0
- package/dist/copilot/provider-shared.js.map +1 -0
- package/dist/copilot/provider.js +15 -0
- package/dist/copilot/provider.js.map +1 -0
- package/dist/copilot/rate-limit.js +77 -0
- package/dist/copilot/rate-limit.js.map +1 -0
- package/dist/copilot/sse.js +69 -0
- package/dist/copilot/sse.js.map +1 -0
- package/dist/copilot/system-prompt.js +113 -0
- package/dist/copilot/system-prompt.js.map +1 -0
- package/dist/copilot/tenant-config.js +145 -0
- package/dist/copilot/tenant-config.js.map +1 -0
- package/dist/copilot/tools.js +241 -0
- package/dist/copilot/tools.js.map +1 -0
- package/dist/copilot/turns-handler.js +322 -0
- package/dist/copilot/turns-handler.js.map +1 -0
- package/dist/observability/otel.js +184 -2
- package/dist/observability/otel.js.map +1 -1
- package/dist/spec/openapi.yaml +82 -1
- package/dist/tools/context/create.js +3 -1
- package/dist/tools/context/create.js.map +1 -1
- package/dist/tools/context/index.js +3 -1
- package/dist/tools/context/index.js.map +1 -1
- package/dist/tools/context/next-step.js +93 -0
- package/dist/tools/context/next-step.js.map +1 -0
- package/dist/tools/source/create.js +3 -1
- package/dist/tools/source/create.js.map +1 -1
- package/dist/transport/body-limit.js +27 -0
- package/dist/transport/body-limit.js.map +1 -1
- package/dist/transport/http.js +35 -25
- package/dist/transport/http.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Read-only tool surface over the matcher operation index.
|
|
2
|
+
//
|
|
3
|
+
// The copilot agent loop offers the model a list of matcher GET operations as
|
|
4
|
+
// JSON-Schema function defs (`buildReadToolDefs`) and — in task 1.2.2 —
|
|
5
|
+
// executes one by operationId through the capability layer. The GET filter is
|
|
6
|
+
// the safe read gate: a mutation can never enter the tool list, so the agent is
|
|
7
|
+
// structurally read-only in Phase 1. POST-shaped reads (e.g. simulateFeeSchedule)
|
|
8
|
+
// are deliberately excluded here and deferred to Phase 4's allow-list.
|
|
9
|
+
//
|
|
10
|
+
// The index is parsed once and memoized (`getIndex`), mirroring server.ts's
|
|
11
|
+
// boot-index memo so the tool-list builder and the executor share one parse and
|
|
12
|
+
// importing this module never touches the filesystem.
|
|
13
|
+
import { requireMatcherClient } from '../auth/request-token.js';
|
|
14
|
+
import { loadSpec } from '../spec/load.js';
|
|
15
|
+
import { InvokeError, Invoker } from '../tools/generic/invoke.js';
|
|
16
|
+
import { isMatcherApiError } from '../tools/tool-error.js';
|
|
17
|
+
/** The boot operation index, parsed once and memoized. Lazy so importing this
|
|
18
|
+
* module reads no files; the spec is immutable boot data. */
|
|
19
|
+
let indexCache;
|
|
20
|
+
export function getIndex() {
|
|
21
|
+
if (indexCache === undefined) {
|
|
22
|
+
indexCache = loadSpec().index;
|
|
23
|
+
}
|
|
24
|
+
return indexCache;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The read + write tool-def lists derived from an operation index, memoized by
|
|
28
|
+
* the index's IDENTITY. `getIndex()` returns one stable object, so the production
|
|
29
|
+
* path is built once; an injected index (tests) is memoized under its own
|
|
30
|
+
* identity, so it never returns another index's stale lists. The def-array
|
|
31
|
+
* synthesis is pure over the immutable index, so caching it is sound.
|
|
32
|
+
*/
|
|
33
|
+
const toolDefsCache = new WeakMap();
|
|
34
|
+
export function getToolDefs(index) {
|
|
35
|
+
let cached = toolDefsCache.get(index);
|
|
36
|
+
if (cached === undefined) {
|
|
37
|
+
cached = { read: buildReadToolDefs(index), write: buildWriteToolDefs(index) };
|
|
38
|
+
toolDefsCache.set(index, cached);
|
|
39
|
+
}
|
|
40
|
+
return cached;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The config write surface the copilot may PROPOSE (never execute). Every entry
|
|
44
|
+
* is a non-GET operation over one of the seven configuration resource families —
|
|
45
|
+
* match rules, reconciliation contexts, fee schedules, fee rules, sources, field
|
|
46
|
+
* maps, and source bindings — across the create/update/delete/archive/restore/
|
|
47
|
+
* reorder/clone verbs the spec declares. This set is the write-barrier's positive
|
|
48
|
+
* gate: an operationId absent from it produces NO write tool def
|
|
49
|
+
* (`buildWriteToolDefs`) and the agent loop never routes a call to the proposal
|
|
50
|
+
* path — structurally unproposable, mirroring how a non-GET op is unreadable.
|
|
51
|
+
* Exception, match-run, ingestion, discovery, and governance mutations are
|
|
52
|
+
* deliberately excluded — scheduling and the simulate/preview POST-shaped
|
|
53
|
+
* "reads" too (they mutate nothing, so they are neither read nor proposed here).
|
|
54
|
+
*/
|
|
55
|
+
export const WRITE_ALLOWLIST = new Set([
|
|
56
|
+
// match rules
|
|
57
|
+
'createMatchRule',
|
|
58
|
+
'updateMatchRule',
|
|
59
|
+
'deleteMatchRule',
|
|
60
|
+
'reorderMatchRules',
|
|
61
|
+
// reconciliation contexts
|
|
62
|
+
'createContext',
|
|
63
|
+
'updateContext',
|
|
64
|
+
'archiveContext',
|
|
65
|
+
'restoreContext',
|
|
66
|
+
'cloneContext',
|
|
67
|
+
// fee schedules
|
|
68
|
+
'createFeeSchedule',
|
|
69
|
+
'updateFeeSchedule',
|
|
70
|
+
'deleteFeeSchedule',
|
|
71
|
+
// fee rules
|
|
72
|
+
'createFeeRule',
|
|
73
|
+
'updateFeeRule',
|
|
74
|
+
'deleteFeeRule',
|
|
75
|
+
// sources
|
|
76
|
+
'createSource',
|
|
77
|
+
'updateSource',
|
|
78
|
+
'archiveSource',
|
|
79
|
+
'restoreSource',
|
|
80
|
+
// field maps
|
|
81
|
+
'createFieldMap',
|
|
82
|
+
'updateFieldMap',
|
|
83
|
+
'deleteFieldMap',
|
|
84
|
+
// source bindings
|
|
85
|
+
'createSourceBinding',
|
|
86
|
+
'updateSourceBinding',
|
|
87
|
+
'deleteSourceBinding',
|
|
88
|
+
]);
|
|
89
|
+
/**
|
|
90
|
+
* Synthesize the JSON Schema (2020-12) an operation's tool def advertises: a
|
|
91
|
+
* `pathParams` object (each name → `{type:'string'}`, all required), a `query`
|
|
92
|
+
* object (from the op's query params, `required` honored), and — when
|
|
93
|
+
* `includeBody` is set and the op declares a request body — a `body` property
|
|
94
|
+
* carrying the op's dereferenced request-body schema verbatim (required, so the
|
|
95
|
+
* model supplies a complete write). Containers are omitted when the op has none,
|
|
96
|
+
* so a param-less op advertises an empty object schema. Reads pass
|
|
97
|
+
* `includeBody: false` (GET carries no body); writes pass `true`.
|
|
98
|
+
*/
|
|
99
|
+
function inputSchemaFor(op, includeBody = false) {
|
|
100
|
+
const properties = {};
|
|
101
|
+
const required = [];
|
|
102
|
+
if (op.pathParams.length > 0) {
|
|
103
|
+
const props = {};
|
|
104
|
+
for (const name of op.pathParams) {
|
|
105
|
+
props[name] = { type: 'string' };
|
|
106
|
+
}
|
|
107
|
+
properties.pathParams = {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: props,
|
|
110
|
+
required: [...op.pathParams],
|
|
111
|
+
};
|
|
112
|
+
required.push('pathParams');
|
|
113
|
+
}
|
|
114
|
+
const queryEntries = Object.entries(op.queryParams);
|
|
115
|
+
if (queryEntries.length > 0) {
|
|
116
|
+
const props = {};
|
|
117
|
+
const requiredQuery = [];
|
|
118
|
+
for (const [name, param] of queryEntries) {
|
|
119
|
+
props[name] = { type: param.type };
|
|
120
|
+
if (param.required) {
|
|
121
|
+
requiredQuery.push(name);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
properties.query = {
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties: props,
|
|
127
|
+
...(requiredQuery.length > 0 ? { required: requiredQuery } : {}),
|
|
128
|
+
};
|
|
129
|
+
if (requiredQuery.length > 0) {
|
|
130
|
+
required.push('query');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (includeBody && op.requestBodySchema !== undefined) {
|
|
134
|
+
properties.body = op.requestBodySchema;
|
|
135
|
+
required.push('body');
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties,
|
|
140
|
+
...(required.length > 0 ? { required } : {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build the read-only tool list: one `LLMToolDef` per GET operation in the
|
|
145
|
+
* index, each with a synthesized JSON-Schema input. The GET filter is the read
|
|
146
|
+
* gate — no mutating operation can appear. Description is the op summary,
|
|
147
|
+
* falling back to `METHOD path` when the spec omits one.
|
|
148
|
+
*/
|
|
149
|
+
export function buildReadToolDefs(index) {
|
|
150
|
+
const defs = [];
|
|
151
|
+
for (const op of index.operations.values()) {
|
|
152
|
+
if (op.method !== 'GET') {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
defs.push({
|
|
156
|
+
name: op.operationId,
|
|
157
|
+
description: op.summary || `${op.method} ${op.path}`,
|
|
158
|
+
inputSchema: inputSchemaFor(op),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return defs;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build the write tool list: one `LLMToolDef` per `WRITE_ALLOWLIST` operation
|
|
165
|
+
* present in the index, each advertising its request body (plus path/query
|
|
166
|
+
* params) so the model can PROPOSE a complete, well-shaped write. This set is
|
|
167
|
+
* disjoint from `buildReadToolDefs` by construction — the allow-list holds only
|
|
168
|
+
* non-GET config ops — so the agent loop routes a call by allow-list membership:
|
|
169
|
+
* a member is proposed (never executed), everything else takes the read path. An
|
|
170
|
+
* operationId absent from the allow-list yields no def and is unproposable.
|
|
171
|
+
*/
|
|
172
|
+
export function buildWriteToolDefs(index) {
|
|
173
|
+
const defs = [];
|
|
174
|
+
for (const op of index.operations.values()) {
|
|
175
|
+
if (!WRITE_ALLOWLIST.has(op.operationId)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
defs.push({
|
|
179
|
+
name: op.operationId,
|
|
180
|
+
description: op.summary || `${op.method} ${op.path}`,
|
|
181
|
+
inputSchema: inputSchemaFor(op, true),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return defs;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Execute one model-requested read tool through the existing capability layer
|
|
188
|
+
* (`Invoker.assemble` + the token-bound `MatcherClient`) and return a
|
|
189
|
+
* `LLMToolResult` the agent loop feeds back into the next turn.
|
|
190
|
+
*
|
|
191
|
+
* The op is looked up and GET-gated first (defense in depth — the tool list is
|
|
192
|
+
* already GET-only), so a mutating operationId is refused before any client is
|
|
193
|
+
* built and can never issue a request. The model's `input` carries `pathParams`
|
|
194
|
+
* and `query` per the schema `buildReadToolDefs` advertises; they are forwarded
|
|
195
|
+
* verbatim to `Invoker.assemble`, which owns path substitution and the tenant
|
|
196
|
+
* guardrail (a tenant_id/tenantId/tenant key is rejected there — the third rail
|
|
197
|
+
* stays enforced, never stripped-and-hidden). This function NEVER throws: every
|
|
198
|
+
* path — success, pre-flight InvokeError, matcher RFC 9457 problem, or an
|
|
199
|
+
* unexpected fault — resolves to a result so the loop always has one per call.
|
|
200
|
+
* The bearer token is never logged and never placed in a returned message.
|
|
201
|
+
*
|
|
202
|
+
* `clientFor` is injectable for tests (mirrors `dispatch`'s param); production
|
|
203
|
+
* passes nothing and gets the request-scoped, fail-closed `requireMatcherClient`.
|
|
204
|
+
*/
|
|
205
|
+
export async function executeReadTool(call, index, clientFor = requireMatcherClient) {
|
|
206
|
+
const op = index.operations.get(call.name);
|
|
207
|
+
if (op === undefined || op.method !== 'GET') {
|
|
208
|
+
return { id: call.id, content: 'operation not available', isError: true };
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const pathParams = call.input.pathParams;
|
|
212
|
+
const query = call.input.query;
|
|
213
|
+
// ponytail: one Invoker per call (a fresh Ajv2020 that a GET never compiles);
|
|
214
|
+
// negligible next to the network round-trip. Memoize by index if tool-call
|
|
215
|
+
// volume ever dominates.
|
|
216
|
+
const request = new Invoker(index).assemble({
|
|
217
|
+
operationId: call.name,
|
|
218
|
+
...(pathParams !== undefined ? { pathParams } : {}),
|
|
219
|
+
...(query !== undefined ? { query } : {}),
|
|
220
|
+
});
|
|
221
|
+
const data = await clientFor().request(request);
|
|
222
|
+
return { id: call.id, content: JSON.stringify(data ?? null) };
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
// Pre-flight failure (unknown op, missing path param, tenant guardrail, …).
|
|
226
|
+
if (err instanceof InvokeError) {
|
|
227
|
+
return { id: call.id, content: err.message, isError: true };
|
|
228
|
+
}
|
|
229
|
+
// Matcher RFC 9457 problem — safe to show the model (never token-bearing).
|
|
230
|
+
if (isMatcherApiError(err)) {
|
|
231
|
+
return {
|
|
232
|
+
id: call.id,
|
|
233
|
+
content: JSON.stringify({ status: err.status, ...err.problem }),
|
|
234
|
+
isError: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Anything unexpected: a fixed, information-free message (no leak).
|
|
238
|
+
return { id: call.id, content: 'tool execution failed', isError: true };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/copilot/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// The `POST /agent/turns` request handler — the copilot's HTTP entry point.
|
|
2
|
+
//
|
|
3
|
+
// It streams one agent turn over Server-Sent Events: parse + validate the
|
|
4
|
+
// stateless `AgentTurnRequest`, resolve the config-selected provider, build the
|
|
5
|
+
// tool list (read ops executed inline + allow-listed write ops the loop only
|
|
6
|
+
// PROPOSES) + matcher system prompt, fail fast on a missing bearer AND probe
|
|
7
|
+
// matcher's auth (one cheap authenticated GET) before constructing any paid
|
|
8
|
+
// provider, then drive the agent loop, forwarding text/tool/proposal/done to the
|
|
9
|
+
// SSE sink. Every
|
|
10
|
+
// named failure (bad body, over-cap body, unconfigured provider, missing bearer,
|
|
11
|
+
// unexpected fault) degrades to a single SSE `error` + `done` — the browser reads
|
|
12
|
+
// SSE, so there is no bare HTTP status to react to once the stream may have begun.
|
|
13
|
+
//
|
|
14
|
+
// Security: the bearer token is resolved from the request-scoped ALS store by
|
|
15
|
+
// `requireMatcherClient` (seeded at the HTTP boundary) and is never read, logged,
|
|
16
|
+
// or emitted here. No tenant identifier is accepted or forwarded — the tenant is
|
|
17
|
+
// implicit in the relayed JWT (the Invoker guardrail enforces it downstream).
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { getRequestBearerToken, getRequestTenantId, requireMatcherClient, UnauthenticatedError, } from '../auth/request-token.js';
|
|
20
|
+
import { MatcherApiError } from '../matcher/client.js';
|
|
21
|
+
import { BodyTooLargeError, readBody } from '../transport/body-limit.js';
|
|
22
|
+
import { runAgentTurn } from './agent.js';
|
|
23
|
+
import { CopilotUnconfiguredError, getProvider } from './provider-factory.js';
|
|
24
|
+
import { RateLimiter } from './rate-limit.js';
|
|
25
|
+
import { createSseSink } from './sse.js';
|
|
26
|
+
import { buildSystemPrompt } from './system-prompt.js';
|
|
27
|
+
import { resolveCopilotConfig } from './tenant-config.js';
|
|
28
|
+
import { getIndex, getToolDefs } from './tools.js';
|
|
29
|
+
/**
|
|
30
|
+
* Process-lifetime rate limiter shared across turns. Buckets are keyed on the
|
|
31
|
+
* key-only tenant id; a turn with no decodable tenant shares one bucket under
|
|
32
|
+
* RATE_LIMIT_ANON_KEY (the BYOC single-tenant / spoofed-token case — bounds a
|
|
33
|
+
* runaway client, not a determined adversary). Per-process → effective cluster
|
|
34
|
+
* limit is this × replica count.
|
|
35
|
+
*/
|
|
36
|
+
const turnRateLimiter = new RateLimiter();
|
|
37
|
+
/** Shared bucket key for turns whose relayed token carries no decodable tenant. */
|
|
38
|
+
const RATE_LIMIT_ANON_KEY = '__no_tenant__';
|
|
39
|
+
/**
|
|
40
|
+
* Auth-probe decision cache: sha256(bearer) → epoch-ms expiry of a matcher-verified
|
|
41
|
+
* "authenticated" verdict. A short TTL lets one probe cover a burst of turns from
|
|
42
|
+
* the same caller instead of one probe per turn. Only SUCCESS is cached — a token
|
|
43
|
+
* matcher rejects re-probes each turn (a cheap Authorize reject; the attacker gains
|
|
44
|
+
* nothing and legit token refresh is never locked out). The raw token is NEVER
|
|
45
|
+
* stored — only its hash keys the map.
|
|
46
|
+
*
|
|
47
|
+
* Bounded at PROBE_CACHE_MAX_ENTRIES: on an insert that finds the map at/over the
|
|
48
|
+
* cap, expired entries are swept first, so a stream of distinct short-lived tokens
|
|
49
|
+
* cannot grow the map for the process lifetime. Exported for the bound test only.
|
|
50
|
+
*/
|
|
51
|
+
const PROBE_CACHE_TTL_MS = 5_000;
|
|
52
|
+
export const PROBE_CACHE_MAX_ENTRIES = 10_000;
|
|
53
|
+
export const probeOkCache = new Map();
|
|
54
|
+
/**
|
|
55
|
+
* The cheapest Bearer-authenticated matcher GET. Matcher's `Authorize` middleware
|
|
56
|
+
* gates `/v1/contexts`, so a forged/expired token is rejected (401/403) here —
|
|
57
|
+
* before any paid LLM call. `limit=1` bounds the matcher-side query; the response
|
|
58
|
+
* body is discarded (only the auth verdict matters).
|
|
59
|
+
*/
|
|
60
|
+
const AUTH_PROBE_REQUEST = {
|
|
61
|
+
method: 'GET',
|
|
62
|
+
path: '/v1/contexts',
|
|
63
|
+
query: { limit: 1 },
|
|
64
|
+
};
|
|
65
|
+
/** Operator remediation shown when matcher REJECTS the relayed token (401/403). */
|
|
66
|
+
const PROBE_REJECTED_REMEDIATION = 'The matcher credential was rejected. Check that your token is valid and has ' +
|
|
67
|
+
'not expired, then retry. This server relays your credential verbatim and holds ' +
|
|
68
|
+
'none of its own.';
|
|
69
|
+
/**
|
|
70
|
+
* Verify the relayed bearer against matcher BEFORE any paid provider call: a
|
|
71
|
+
* JWT-shaped but unauthorized token must not be able to spend LLM budget. Issues
|
|
72
|
+
* ONE authenticated GET (matcher's `Authorize` gates it) unless a fresh cached
|
|
73
|
+
* verdict exists. Returns `true` when matcher accepts the token, `false` when it
|
|
74
|
+
* rejects it (401/403). Any OTHER matcher failure (5xx, transport) is rethrown to
|
|
75
|
+
* the caller's last-resort guard — we fail closed rather than burn budget against
|
|
76
|
+
* an unverifiable backend. The raw token is never stored or logged; only its
|
|
77
|
+
* sha256 hash keys the cache.
|
|
78
|
+
*/
|
|
79
|
+
async function probeMatcherAuth(client) {
|
|
80
|
+
const token = getRequestBearerToken();
|
|
81
|
+
const cacheKey = token !== undefined
|
|
82
|
+
? createHash('sha256').update(token).digest('hex')
|
|
83
|
+
: undefined;
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
if (cacheKey !== undefined && (probeOkCache.get(cacheKey) ?? 0) > now) {
|
|
86
|
+
return true; // fresh authenticated verdict — skip the probe
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await client.request(AUTH_PROBE_REQUEST);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof MatcherApiError &&
|
|
93
|
+
(err.status === 401 || err.status === 403)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
if (cacheKey !== undefined) {
|
|
99
|
+
// ponytail: sweep-expired-on-insert-past-cap. The O(n) sweep runs only when the
|
|
100
|
+
// map is at the cap; with a 5s TTL, >10k live entries within that window is not
|
|
101
|
+
// a real load, so no LRU. If it ever is, evict oldest-by-expiry here.
|
|
102
|
+
if (probeOkCache.size >= PROBE_CACHE_MAX_ENTRIES) {
|
|
103
|
+
for (const [key, expiry] of probeOkCache) {
|
|
104
|
+
if (expiry <= now) {
|
|
105
|
+
probeOkCache.delete(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
probeOkCache.set(cacheKey, now + PROBE_CACHE_TTL_MS);
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The messages the agent loop receives. Browser-supplied `screenContext` is
|
|
115
|
+
* injected as a synthetic FIRST user message — the SAME (untrusted) trust tier as
|
|
116
|
+
* the operator's own input — rather than folded into the system prompt, so a
|
|
117
|
+
* crafted route/summary can never reach the higher-trust system tier. The label
|
|
118
|
+
* is a plain statement of fact, not an instruction.
|
|
119
|
+
*/
|
|
120
|
+
function buildTurnMessages(request) {
|
|
121
|
+
const { screenContext } = request;
|
|
122
|
+
if (screenContext === undefined) {
|
|
123
|
+
return request.messages;
|
|
124
|
+
}
|
|
125
|
+
const parts = [`route ${screenContext.route}`];
|
|
126
|
+
if (screenContext.contextId !== undefined) {
|
|
127
|
+
parts.push(`reconciliation context ${screenContext.contextId}`);
|
|
128
|
+
}
|
|
129
|
+
if (screenContext.summary !== undefined) {
|
|
130
|
+
parts.push(screenContext.summary);
|
|
131
|
+
}
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
role: 'user',
|
|
135
|
+
content: `[screen context] The operator is currently viewing: ${parts.join(' — ')}.`,
|
|
136
|
+
},
|
|
137
|
+
...request.messages,
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validate an unknown parsed body as an `AgentTurnRequest`. Returns the typed
|
|
142
|
+
* request on success, or `undefined` when the shape is wrong (non-empty
|
|
143
|
+
* `messages` of `{role,content}` with role in {user,assistant}). A malformed
|
|
144
|
+
* `screenContext` is dropped rather than rejecting the turn.
|
|
145
|
+
*/
|
|
146
|
+
function parseAgentTurnRequest(body) {
|
|
147
|
+
if (typeof body !== 'object' || body === null) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const record = body;
|
|
151
|
+
const { messages } = record;
|
|
152
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
for (const message of messages) {
|
|
156
|
+
if (typeof message !== 'object' || message === null) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const { role, content } = message;
|
|
160
|
+
if (role !== 'user' && role !== 'assistant') {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
if (typeof content !== 'string') {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const screenContext = parseScreenContext(record.screenContext);
|
|
168
|
+
return {
|
|
169
|
+
messages: messages,
|
|
170
|
+
...(screenContext !== undefined ? { screenContext } : {}),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Parse an optional `screenContext`; drops it unless it carries a string `route`. */
|
|
174
|
+
function parseScreenContext(raw) {
|
|
175
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
const record = raw;
|
|
179
|
+
if (typeof record.route !== 'string') {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
route: record.route,
|
|
184
|
+
...(typeof record.contextId === 'string' ? { contextId: record.contextId } : {}),
|
|
185
|
+
...(typeof record.summary === 'string' ? { summary: record.summary } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Handle one `POST /agent/turns` request, streaming the turn over SSE. Resolves
|
|
190
|
+
* once the turn is fully written (a terminal `done` has been emitted). Does not
|
|
191
|
+
* rethrow: any unexpected fault is caught and surfaced as a single `error`+`done`
|
|
192
|
+
* so the boundary never has to answer a half-streamed response.
|
|
193
|
+
*/
|
|
194
|
+
export async function handleAgentTurn(config, req, res, deps = {}) {
|
|
195
|
+
const sink = createSseSink(res);
|
|
196
|
+
try {
|
|
197
|
+
// Fail fast on a missing/blank bearer before any body parsing or provider
|
|
198
|
+
// construction — the cheapest gate first, and so an unauthenticated probe
|
|
199
|
+
// learns nothing about the server's config (e.g. copilot_unconfigured). The
|
|
200
|
+
// client built here is REUSED for the pre-provider auth probe below (the read
|
|
201
|
+
// tools still resolve their own per-call client from the same request store).
|
|
202
|
+
let matcherClient;
|
|
203
|
+
try {
|
|
204
|
+
matcherClient = (deps.requireClient ??
|
|
205
|
+
(() => requireMatcherClient(undefined, config)))();
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (err instanceof UnauthenticatedError) {
|
|
209
|
+
sink.error('unauthenticated', err.message);
|
|
210
|
+
sink.done('end');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
const contentType = (req.headers['content-type'] ?? '').toLowerCase();
|
|
216
|
+
if (!contentType.includes('application/json')) {
|
|
217
|
+
sink.error('bad_request', 'content-type must be application/json');
|
|
218
|
+
sink.done('end');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
let body;
|
|
222
|
+
try {
|
|
223
|
+
const raw = await readBody(req, config.maxBodyBytes);
|
|
224
|
+
body = raw.length > 0 ? JSON.parse(raw) : undefined;
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
if (err instanceof BodyTooLargeError) {
|
|
228
|
+
sink.error('payload_too_large', 'request body is too large');
|
|
229
|
+
sink.done('end');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// JSON parse failure or a read error — a client fault, no internal detail.
|
|
233
|
+
sink.error('bad_request', 'request body is not valid JSON');
|
|
234
|
+
sink.done('end');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const request = parseAgentTurnRequest(body);
|
|
238
|
+
if (request === undefined) {
|
|
239
|
+
sink.error('bad_request', 'messages must be a non-empty array of {role,content}, role one of user|assistant');
|
|
240
|
+
sink.done('end');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// Probe matcher's `Authorize` with the relayed token BEFORE any tenant-derived
|
|
244
|
+
// work AND before constructing the paid provider (M1): a JWT-shaped-but-
|
|
245
|
+
// unauthorized token is rejected here and never reaches a billable LLM call —
|
|
246
|
+
// and, crucially, never selects a config slice or consumes a rate bucket, so a
|
|
247
|
+
// forged `tenant_id` cannot target a victim tenant's quota. Runs right after the
|
|
248
|
+
// body parse; probe volume is floored by the no-bearer reject in
|
|
249
|
+
// `requireMatcherClient`. A fresh cached verdict (keyed by token hash) skips the
|
|
250
|
+
// probe entirely.
|
|
251
|
+
if (!(await probeMatcherAuth(matcherClient))) {
|
|
252
|
+
sink.error('unauthenticated', PROBE_REJECTED_REMEDIATION);
|
|
253
|
+
sink.done('end');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Resolve the per-tenant config slice from the key-only tenant id decoded off
|
|
257
|
+
// the relayed JWT (getRequestTenantId). The token is already matcher-verified by
|
|
258
|
+
// the probe above, so a forged `tenant_id` can no longer reach this point to
|
|
259
|
+
// select a slice or key a bucket. A tenant with an override runs its own
|
|
260
|
+
// provider/model/key/limits; a tenant with no entry — or no decodable tenant —
|
|
261
|
+
// falls back to the global env config unchanged. The tenant is NEVER used for
|
|
262
|
+
// authorization here (matcher owns that via the relayed token); it only selects
|
|
263
|
+
// the config slice this turn uses and keys the rate bucket.
|
|
264
|
+
const tenantId = getRequestTenantId();
|
|
265
|
+
const resolvedConfig = resolveCopilotConfig(config, tenantId);
|
|
266
|
+
// Rate-limit gate: keyed on the now-verified tenant, applied before any provider
|
|
267
|
+
// spin-up so a rejected caller never triggers an LLM call. On limit → a single
|
|
268
|
+
// `rate_limited` SSE error + terminal done (a non-calm code → renders as
|
|
269
|
+
// TurnError in the UI, no UI change required). Sits after the auth probe, so a
|
|
270
|
+
// forged token is rejected as `unauthenticated` before it can touch a bucket.
|
|
271
|
+
const decision = (deps.rateLimiter ?? turnRateLimiter).check(tenantId ?? RATE_LIMIT_ANON_KEY, resolvedConfig.copilotRateLimitPerMin, resolvedConfig.copilotRateLimitBurst);
|
|
272
|
+
if (!decision.allowed) {
|
|
273
|
+
sink.error('rate_limited', `too many copilot requests; retry in ${decision.retryAfterSeconds}s`);
|
|
274
|
+
sink.done('end');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
let provider;
|
|
278
|
+
try {
|
|
279
|
+
provider = (deps.resolveProvider ?? getProvider)(resolvedConfig);
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
if (err instanceof CopilotUnconfiguredError) {
|
|
283
|
+
sink.error('copilot_unconfigured', 'the copilot is not configured on this server');
|
|
284
|
+
sink.done('end');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
throw err;
|
|
288
|
+
}
|
|
289
|
+
const index = deps.index ?? getIndex();
|
|
290
|
+
// Reads execute inline; allow-listed writes are PROPOSED (never executed) by
|
|
291
|
+
// the agent loop's write-barrier. Both classes must be advertised or the model
|
|
292
|
+
// can never emit a write call and the proposal path is dead. The def lists are
|
|
293
|
+
// memoized by index identity (getToolDefs), so this is a cache hit per turn.
|
|
294
|
+
const { read, write } = getToolDefs(index);
|
|
295
|
+
await (deps.runTurn ?? runAgentTurn)({
|
|
296
|
+
provider,
|
|
297
|
+
tools: [...read, ...write],
|
|
298
|
+
index,
|
|
299
|
+
system: buildSystemPrompt(),
|
|
300
|
+
messages: buildTurnMessages(request),
|
|
301
|
+
sink,
|
|
302
|
+
maxIterations: resolvedConfig.copilotMaxToolIterations,
|
|
303
|
+
maxTurnTokens: resolvedConfig.copilotMaxTurnTokens,
|
|
304
|
+
// Telemetry labels (4.2.1): the instrumentation lives in runAgentTurn but is
|
|
305
|
+
// inert unless fed the resolved provider id, model, and key-only tenant.
|
|
306
|
+
providerName: resolvedConfig.copilotProvider,
|
|
307
|
+
...(resolvedConfig.copilotModel !== undefined
|
|
308
|
+
? { model: resolvedConfig.copilotModel }
|
|
309
|
+
: {}),
|
|
310
|
+
...(tenantId !== undefined ? { tenant: tenantId } : {}),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
// Last-resort guard: a single safe SSE error + done, never a token or stack.
|
|
315
|
+
sink.error('internal', 'the copilot encountered an internal error');
|
|
316
|
+
sink.done('end');
|
|
317
|
+
// Log only the message string, never the raw error object — a provider-SDK
|
|
318
|
+
// error can carry request context (headers, config) we must not log wholesale.
|
|
319
|
+
console.error('matcher-mcp agent turn failed:', err instanceof Error ? err.message : String(err));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=turns-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns-handler.js","sourceRoot":"","sources":["../../src/copilot/turns-handler.ts"],"names":[],"mappings":"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"}
|