@ggui-ai/negotiator 0.1.0-rc.1
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/LICENSE +201 -0
- package/README.md +49 -0
- package/dist/contract-hash.d.ts +54 -0
- package/dist/contract-hash.d.ts.map +1 -0
- package/dist/contract-hash.js +96 -0
- package/dist/contract-validators.d.ts +171 -0
- package/dist/contract-validators.d.ts.map +1 -0
- package/dist/contract-validators.js +478 -0
- package/dist/decision-input.d.ts +48 -0
- package/dist/decision-input.d.ts.map +1 -0
- package/dist/decision-input.js +14 -0
- package/dist/decision.d.ts +54 -0
- package/dist/decision.d.ts.map +1 -0
- package/dist/decision.js +500 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/intent.d.ts +22 -0
- package/dist/intent.d.ts.map +1 -0
- package/dist/intent.js +28 -0
- package/dist/llm-caller.d.ts +70 -0
- package/dist/llm-caller.d.ts.map +1 -0
- package/dist/llm-caller.js +38 -0
- package/dist/llm-rerank.d.ts +101 -0
- package/dist/llm-rerank.d.ts.map +1 -0
- package/dist/llm-rerank.js +178 -0
- package/dist/negotiate.d.ts +141 -0
- package/dist/negotiate.d.ts.map +1 -0
- package/dist/negotiate.js +161 -0
- package/dist/normalize-schema.d.ts +22 -0
- package/dist/normalize-schema.d.ts.map +1 -0
- package/dist/normalize-schema.js +191 -0
- package/dist/pure.d.ts +30 -0
- package/dist/pure.d.ts.map +1 -0
- package/dist/pure.js +43 -0
- package/dist/rag-search.d.ts +73 -0
- package/dist/rag-search.d.ts.map +1 -0
- package/dist/rag-search.js +192 -0
- package/dist/rerank-eval/pairs.d.ts +28 -0
- package/dist/rerank-eval/pairs.d.ts.map +1 -0
- package/dist/rerank-eval/pairs.js +531 -0
- package/dist/rerank-eval/run-probe-cli.d.ts +3 -0
- package/dist/rerank-eval/run-probe-cli.d.ts.map +1 -0
- package/dist/rerank-eval/run-probe-cli.js +146 -0
- package/dist/rerank-eval/run-probe.d.ts +68 -0
- package/dist/rerank-eval/run-probe.d.ts.map +1 -0
- package/dist/rerank-eval/run-probe.js +113 -0
- package/dist/session.d.ts +42 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +21 -0
- package/dist/suggestion.d.ts +38 -0
- package/dist/suggestion.d.ts.map +1 -0
- package/dist/suggestion.js +47 -0
- package/dist/synth-bench/corpus.d.ts +106 -0
- package/dist/synth-bench/corpus.d.ts.map +1 -0
- package/dist/synth-bench/corpus.js +994 -0
- package/dist/synth-bench/run-bench-cli.d.ts +3 -0
- package/dist/synth-bench/run-bench-cli.d.ts.map +1 -0
- package/dist/synth-bench/run-bench-cli.js +181 -0
- package/dist/synth-bench/run-bench.d.ts +101 -0
- package/dist/synth-bench/run-bench.d.ts.map +1 -0
- package/dist/synth-bench/run-bench.js +374 -0
- package/dist/synthesize-contract.d.ts +131 -0
- package/dist/synthesize-contract.d.ts.map +1 -0
- package/dist/synthesize-contract.js +948 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/package.json +74 -0
- package/src/contract-hash.ts +102 -0
- package/src/contract-validators.ts +604 -0
- package/src/decision-input.ts +49 -0
- package/src/decision.ts +581 -0
- package/src/index.ts +63 -0
- package/src/intent.ts +37 -0
- package/src/llm-caller.ts +82 -0
- package/src/llm-rerank.ts +280 -0
- package/src/negotiate.ts +312 -0
- package/src/normalize-schema.ts +193 -0
- package/src/pure.ts +46 -0
- package/src/rag-search.ts +274 -0
- package/src/rerank-eval/pairs.ts +624 -0
- package/src/rerank-eval/run-probe-cli.ts +197 -0
- package/src/rerank-eval/run-probe.ts +198 -0
- package/src/session.ts +41 -0
- package/src/suggestion.ts +73 -0
- package/src/synth-bench/corpus.ts +1126 -0
- package/src/synth-bench/run-bench-cli.ts +237 -0
- package/src/synth-bench/run-bench.ts +525 -0
- package/src/synthesize-contract.ts +1161 -0
- package/src/types.ts +31 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Negotiator — top-level orchestrator over the public storage +
|
|
3
|
+
* decision-engine seams.
|
|
4
|
+
*
|
|
5
|
+
* Pipeline:
|
|
6
|
+
* 1. RAG search (per-scope + optional shared pool) via
|
|
7
|
+
* {@link ragSearch} — composes `EmbeddingProvider.embed` +
|
|
8
|
+
* `VectorStore.query` from `@ggui-ai/mcp-server-core`.
|
|
9
|
+
* 2. Read session state (optional injectable).
|
|
10
|
+
* 3. Fast-path for exact blueprint hits — skip the decision LLM.
|
|
11
|
+
* 4. Otherwise call {@link makeDecision} with the RAG candidates and
|
|
12
|
+
* session stack; fold the picked blueprint's pool provenance into
|
|
13
|
+
* the return value.
|
|
14
|
+
*
|
|
15
|
+
* Timing logs (stable format — consumed by benchmarks):
|
|
16
|
+
* [negotiate] embedding: Xms | search: Xms | candidates: N
|
|
17
|
+
* [negotiate] candidate: <id8> | <description> | hash=<h16>
|
|
18
|
+
* [negotiate] FAST PATH: blueprint=<id8> hash=<h12> | LLM decision: 0ms | total: Xms
|
|
19
|
+
* [negotiate] LLM PATH: action=X blueprint=<id8> hash=<h12> | LLM decision: Xms | total: Xms
|
|
20
|
+
*
|
|
21
|
+
* ### Public surface + semver weight
|
|
22
|
+
*
|
|
23
|
+
* Exported:
|
|
24
|
+
* - `negotiate(deps, input)` — runtime orchestrator.
|
|
25
|
+
* - `NegotiateDeps` — injection shape (embedding / vectors / llm +
|
|
26
|
+
* optional session-state reader + optional progress callback).
|
|
27
|
+
* - `NegotiateInput` — agent signal + config.
|
|
28
|
+
* - `NegotiateConfig` — minimum fields the orchestrator actually
|
|
29
|
+
* reads. Pool selection is expressed as `includeSharedPool:
|
|
30
|
+
* boolean` rather than a `poolMode` enum, so callers decide
|
|
31
|
+
* shared-pool inclusion explicitly at each call site.
|
|
32
|
+
* - `NegotiateResult` — the decision result returned to callers.
|
|
33
|
+
*
|
|
34
|
+
* ### Why this package, not `mcp-server-core`
|
|
35
|
+
*
|
|
36
|
+
* `mcp-server-core` locks storage/runtime seams MCP server
|
|
37
|
+
* implementers bind against (`EmbeddingProvider`, `VectorStore`,
|
|
38
|
+
* `Negotiator`). `negotiate()` is a *composition* over those seams —
|
|
39
|
+
* decision-engine semantics, not a new seam. Adding it to
|
|
40
|
+
* `mcp-server-core` would drag the LLM prompts + tool-schemas into a
|
|
41
|
+
* package whose job is to stay minimal and runtime-agnostic.
|
|
42
|
+
*/
|
|
43
|
+
import { ragSearch } from './rag-search.js';
|
|
44
|
+
import { makeDecision } from './decision.js';
|
|
45
|
+
/** Empty session state for cold starts and benchmarks. */
|
|
46
|
+
const EMPTY_SESSION = {
|
|
47
|
+
stack: [],
|
|
48
|
+
conversationHistory: [],
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Orchestrate one negotiation call — RAG search, session read, fast
|
|
52
|
+
* path, decision LLM. See module docstring for the pipeline outline.
|
|
53
|
+
*/
|
|
54
|
+
export async function negotiate(deps, input) {
|
|
55
|
+
const { agent, config } = input;
|
|
56
|
+
const negotiateStart = Date.now();
|
|
57
|
+
deps.onProgress?.('negotiating', 'Analyzing request...');
|
|
58
|
+
// Step 1: RAG search (per-app + optional shared pool in parallel).
|
|
59
|
+
// `embedding` + `vectors` are optional. When either is missing,
|
|
60
|
+
// ragSearch is a no-op (returns empty options + 0 latency); the
|
|
61
|
+
// decision LLM runs against zero candidates and falls back to
|
|
62
|
+
// "create" via its standard branching. OSS without RAG infrastructure
|
|
63
|
+
// gets useful negotiation from the decision LLM alone.
|
|
64
|
+
const queryText = agent.prompt ?? JSON.stringify(agent.data ?? {});
|
|
65
|
+
const emptyResult = { options: [], embeddingLatencyMs: 0, searchLatencyMs: 0 };
|
|
66
|
+
const ragDeps = deps.embedding !== undefined && deps.vectors !== undefined
|
|
67
|
+
? { embedding: deps.embedding, vectors: deps.vectors }
|
|
68
|
+
: undefined;
|
|
69
|
+
const [appResult, sharedResult] = await Promise.all([
|
|
70
|
+
ragDeps
|
|
71
|
+
? ragSearch(ragDeps, { prompt: queryText, scope: config.appId })
|
|
72
|
+
: Promise.resolve(emptyResult),
|
|
73
|
+
ragDeps && config.includeSharedPool
|
|
74
|
+
? ragSearch(ragDeps, { prompt: queryText, scope: 'shared' })
|
|
75
|
+
: Promise.resolve(emptyResult),
|
|
76
|
+
]);
|
|
77
|
+
const ragResult = {
|
|
78
|
+
options: [...appResult.options, ...sharedResult.options],
|
|
79
|
+
embeddingLatencyMs: Math.max(appResult.embeddingLatencyMs, sharedResult.embeddingLatencyMs),
|
|
80
|
+
searchLatencyMs: Math.max(appResult.searchLatencyMs, sharedResult.searchLatencyMs),
|
|
81
|
+
};
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
console.log(`[negotiate] embedding: ${ragResult.embeddingLatencyMs}ms | search: ${ragResult.searchLatencyMs}ms | candidates: ${ragResult.options.length}`);
|
|
84
|
+
for (const opt of ragResult.options) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.log(`[negotiate] candidate: ${opt.blueprintId?.slice(-8) ?? 'none'} | ${opt.description.slice(0, 80)} | hash=${opt.contractHash?.slice(0, 16) ?? 'none'}`);
|
|
87
|
+
}
|
|
88
|
+
deps.onProgress?.('blueprint_search', ragResult.options.length > 0
|
|
89
|
+
? `Found ${ragResult.options.length} blueprint candidate${ragResult.options.length > 1 ? 's' : ''}`
|
|
90
|
+
: 'No blueprints found');
|
|
91
|
+
// Step 2: Read session state (falls back to EMPTY_SESSION).
|
|
92
|
+
const sessionState = deps.readSessionState
|
|
93
|
+
? ((await deps.readSessionState(config.sessionId)) ?? EMPTY_SESSION)
|
|
94
|
+
: EMPTY_SESSION;
|
|
95
|
+
// Step 3: Fast-path for high-confidence exact matches — skip decision LLM.
|
|
96
|
+
const exactOpt = ragResult.options.find((opt) => opt.description.includes('exact'));
|
|
97
|
+
if (exactOpt) {
|
|
98
|
+
const stackHasSameType = sessionState.stack.some((item) => item.prompt && agent.prompt && item.prompt === agent.prompt);
|
|
99
|
+
const action = stackHasSameType ? 'update' : 'create';
|
|
100
|
+
const totalMs = Date.now() - negotiateStart;
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.log(`[negotiate] FAST PATH: blueprint=${exactOpt.blueprintId?.slice(-8)} hash=${exactOpt.contractHash?.slice(0, 12) ?? 'none'} | LLM decision: 0ms | total: ${totalMs}ms`);
|
|
103
|
+
deps.onProgress?.('deciding', 'Exact match found — fast path');
|
|
104
|
+
// The agent's prompt is the outer-pipeline intent (`intent` is not
|
|
105
|
+
// a contract field). Fallback contract is the empty contract — the
|
|
106
|
+
// four-spec surface is omitted entirely (no props, no actions, no
|
|
107
|
+
// streams, no context).
|
|
108
|
+
const fallbackContract = {};
|
|
109
|
+
return {
|
|
110
|
+
decision: {
|
|
111
|
+
action,
|
|
112
|
+
reasoning: `Exact blueprint match (high confidence). ${action === 'update' ? 'Updating existing view.' : 'Creating new view.'}`,
|
|
113
|
+
blueprintId: exactOpt.blueprintId,
|
|
114
|
+
contract: exactOpt.contract ?? fallbackContract,
|
|
115
|
+
},
|
|
116
|
+
alternatives: [],
|
|
117
|
+
storedContractHash: exactOpt.contractHash,
|
|
118
|
+
storedPoolSource: exactOpt.poolSource,
|
|
119
|
+
embeddingLatencyMs: ragResult.embeddingLatencyMs,
|
|
120
|
+
searchLatencyMs: ragResult.searchLatencyMs,
|
|
121
|
+
decisionLatencyMs: 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// Step 4: Build decision input + call the LLM.
|
|
125
|
+
const decisionInput = {
|
|
126
|
+
agentData: agent.data,
|
|
127
|
+
agentPrompt: agent.prompt,
|
|
128
|
+
agentContext: agent.context,
|
|
129
|
+
agentTools: agent.agentTools,
|
|
130
|
+
...(agent.gadgets
|
|
131
|
+
? { gadgets: agent.gadgets }
|
|
132
|
+
: {}),
|
|
133
|
+
sessionState,
|
|
134
|
+
blueprintCandidates: ragResult.options.map((opt) => ({
|
|
135
|
+
blueprintId: opt.blueprintId ?? opt.id,
|
|
136
|
+
description: opt.description,
|
|
137
|
+
contract: opt.contract,
|
|
138
|
+
similarity: parseFloat(opt.description.match(/similarity: (\d+)%/)?.[1] ?? '0') / 100,
|
|
139
|
+
verdict: (opt.description.includes('exact') ? 'exact' : 'partial'),
|
|
140
|
+
})),
|
|
141
|
+
};
|
|
142
|
+
deps.onProgress?.('deciding', 'Choosing the best UI approach...');
|
|
143
|
+
const decisionStart = Date.now();
|
|
144
|
+
const { decision, alternatives } = await makeDecision(decisionInput, deps.llm);
|
|
145
|
+
const decisionLatencyMs = Date.now() - decisionStart;
|
|
146
|
+
const totalMs = Date.now() - negotiateStart;
|
|
147
|
+
const pickedBlueprint = decision.blueprintId
|
|
148
|
+
? ragResult.options.find((opt) => (opt.blueprintId ?? opt.id) === decision.blueprintId)
|
|
149
|
+
: undefined;
|
|
150
|
+
// eslint-disable-next-line no-console
|
|
151
|
+
console.log(`[negotiate] LLM PATH: action=${decision.action} blueprint=${decision.blueprintId?.slice(-8) ?? 'none'} hash=${pickedBlueprint?.contractHash?.slice(0, 12) ?? 'none'} | LLM decision: ${decisionLatencyMs}ms | total: ${totalMs}ms`);
|
|
152
|
+
return {
|
|
153
|
+
decision,
|
|
154
|
+
alternatives,
|
|
155
|
+
storedContractHash: pickedBlueprint?.contractHash,
|
|
156
|
+
storedPoolSource: pickedBlueprint?.poolSource,
|
|
157
|
+
embeddingLatencyMs: ragResult.embeddingLatencyMs,
|
|
158
|
+
searchLatencyMs: ragResult.searchLatencyMs,
|
|
159
|
+
decisionLatencyMs,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic repair of the invalid JSON Schema `type` spellings an
|
|
3
|
+
* LLM occasionally emits in a synthesized contract. Runs inside
|
|
4
|
+
* `buildContract` (before the `dataContractSchema` validation gate) so
|
|
5
|
+
* the dominant synth-decline class — a `type` value outside the seven
|
|
6
|
+
* JSON Schema primitives — is structurally eliminated rather than left
|
|
7
|
+
* to a repair retry.
|
|
8
|
+
*
|
|
9
|
+
* Pure, recursive, total: a schema that is already valid passes
|
|
10
|
+
* through unchanged — every mapping fires only on a value NOT in the
|
|
11
|
+
* valid set. Recursion descends only into sub-schema positions, so
|
|
12
|
+
* data-value positions (`default`, `const`, `enum`, `examples`) are
|
|
13
|
+
* left verbatim — a `default` of `{type: "list"}` is user data, not a
|
|
14
|
+
* schema, and must not be rewritten.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Recursively normalize a JSON Schema, repairing invalid `type`
|
|
18
|
+
* values. Non-object input (booleans, primitives) passes through —
|
|
19
|
+
* `additionalProperties: false` and the like are valid as-is.
|
|
20
|
+
*/
|
|
21
|
+
export declare function normalizeSchema(schema: unknown): unknown;
|
|
22
|
+
//# sourceMappingURL=normalize-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-schema.d.ts","sourceRoot":"","sources":["../src/normalize-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkJH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CA2BxD"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic repair of the invalid JSON Schema `type` spellings an
|
|
3
|
+
* LLM occasionally emits in a synthesized contract. Runs inside
|
|
4
|
+
* `buildContract` (before the `dataContractSchema` validation gate) so
|
|
5
|
+
* the dominant synth-decline class — a `type` value outside the seven
|
|
6
|
+
* JSON Schema primitives — is structurally eliminated rather than left
|
|
7
|
+
* to a repair retry.
|
|
8
|
+
*
|
|
9
|
+
* Pure, recursive, total: a schema that is already valid passes
|
|
10
|
+
* through unchanged — every mapping fires only on a value NOT in the
|
|
11
|
+
* valid set. Recursion descends only into sub-schema positions, so
|
|
12
|
+
* data-value positions (`default`, `const`, `enum`, `examples`) are
|
|
13
|
+
* left verbatim — a `default` of `{type: "list"}` is user data, not a
|
|
14
|
+
* schema, and must not be rewritten.
|
|
15
|
+
*/
|
|
16
|
+
/** The seven JSON Schema primitive type names. */
|
|
17
|
+
const VALID_TYPES = new Set([
|
|
18
|
+
'string',
|
|
19
|
+
'number',
|
|
20
|
+
'integer',
|
|
21
|
+
'boolean',
|
|
22
|
+
'array',
|
|
23
|
+
'object',
|
|
24
|
+
'null',
|
|
25
|
+
]);
|
|
26
|
+
/** Unambiguous non-canonical type spellings → canonical primitive. */
|
|
27
|
+
const TYPE_ALIASES = {
|
|
28
|
+
list: 'array',
|
|
29
|
+
tuple: 'array',
|
|
30
|
+
sequence: 'array',
|
|
31
|
+
dict: 'object',
|
|
32
|
+
map: 'object',
|
|
33
|
+
record: 'object',
|
|
34
|
+
hash: 'object',
|
|
35
|
+
str: 'string',
|
|
36
|
+
text: 'string',
|
|
37
|
+
char: 'string',
|
|
38
|
+
int: 'integer',
|
|
39
|
+
long: 'integer',
|
|
40
|
+
short: 'integer',
|
|
41
|
+
float: 'number',
|
|
42
|
+
double: 'number',
|
|
43
|
+
decimal: 'number',
|
|
44
|
+
num: 'number',
|
|
45
|
+
numeric: 'number',
|
|
46
|
+
bool: 'boolean',
|
|
47
|
+
};
|
|
48
|
+
/** Type words that mean "no constraint" — the `type` key is dropped. */
|
|
49
|
+
const DROP_TYPES = new Set(['any', 'unknown', 'mixed', 'void']);
|
|
50
|
+
/** Object keys whose value is itself a single schema. */
|
|
51
|
+
const SCHEMA_VALUED_KEYS = new Set([
|
|
52
|
+
'items',
|
|
53
|
+
'additionalProperties',
|
|
54
|
+
'not',
|
|
55
|
+
'contains',
|
|
56
|
+
'propertyNames',
|
|
57
|
+
]);
|
|
58
|
+
/** Object keys whose value is a map of name → schema. */
|
|
59
|
+
const SCHEMA_MAP_KEYS = new Set([
|
|
60
|
+
'properties',
|
|
61
|
+
'patternProperties',
|
|
62
|
+
'$defs',
|
|
63
|
+
'definitions',
|
|
64
|
+
]);
|
|
65
|
+
/** Object keys whose value is an array of schemas. */
|
|
66
|
+
const SCHEMA_ARRAY_KEYS = new Set([
|
|
67
|
+
'allOf',
|
|
68
|
+
'anyOf',
|
|
69
|
+
'oneOf',
|
|
70
|
+
'prefixItems',
|
|
71
|
+
]);
|
|
72
|
+
/**
|
|
73
|
+
* Infer the base type of an `enum`-constrained field from its value
|
|
74
|
+
* list. Mixed / empty / non-array → `string` (the safe default, and
|
|
75
|
+
* the overwhelmingly common enum shape).
|
|
76
|
+
*/
|
|
77
|
+
function inferEnumBaseType(enumSibling) {
|
|
78
|
+
if (!Array.isArray(enumSibling) || enumSibling.length === 0) {
|
|
79
|
+
return 'string';
|
|
80
|
+
}
|
|
81
|
+
if (enumSibling.every((v) => typeof v === 'string'))
|
|
82
|
+
return 'string';
|
|
83
|
+
if (enumSibling.every((v) => typeof v === 'number'))
|
|
84
|
+
return 'number';
|
|
85
|
+
if (enumSibling.every((v) => typeof v === 'boolean'))
|
|
86
|
+
return 'boolean';
|
|
87
|
+
return 'string';
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Map one `type` value to a canonical primitive, or `undefined` when
|
|
91
|
+
* the `type` key should be dropped entirely (an absent `type` is valid
|
|
92
|
+
* JSON Schema — it simply imposes no constraint).
|
|
93
|
+
*
|
|
94
|
+
* `enumSibling` is the schema's `enum` array, used to pick the base
|
|
95
|
+
* type when the LLM wrote the (invalid) `type: "enum"`.
|
|
96
|
+
*/
|
|
97
|
+
function normalizeTypeValue(value, enumSibling) {
|
|
98
|
+
if (typeof value === 'string') {
|
|
99
|
+
const lower = value.toLowerCase();
|
|
100
|
+
if (VALID_TYPES.has(lower))
|
|
101
|
+
return lower;
|
|
102
|
+
if (lower === 'enum')
|
|
103
|
+
return inferEnumBaseType(enumSibling);
|
|
104
|
+
if (DROP_TYPES.has(lower))
|
|
105
|
+
return undefined;
|
|
106
|
+
const alias = TYPE_ALIASES[lower];
|
|
107
|
+
if (alias !== undefined)
|
|
108
|
+
return alias;
|
|
109
|
+
// Unrecognized garbage — drop the constraint rather than guess.
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
// Union type (`["string", "null"]`). The protocol contract schema
|
|
114
|
+
// accepts only a single string; recover the first valid non-null
|
|
115
|
+
// member, dropping the nullable arm.
|
|
116
|
+
for (const member of value) {
|
|
117
|
+
if (typeof member === 'string') {
|
|
118
|
+
const norm = normalizeTypeValue(member, enumSibling);
|
|
119
|
+
if (norm !== undefined && norm !== 'null')
|
|
120
|
+
return norm;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return 'string';
|
|
124
|
+
}
|
|
125
|
+
// `type` was a number / object / boolean — meaningless; drop it.
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
/** Normalize a map of name → schema (e.g. `properties`). */
|
|
129
|
+
function normalizeSchemaMap(value) {
|
|
130
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
const out = {};
|
|
134
|
+
for (const [name, sub] of Object.entries(value)) {
|
|
135
|
+
out[name] = coerceToSchema(sub);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Coerce a value found in a schema-expecting position into a schema
|
|
141
|
+
* object. An LLM sometimes writes the shorthand `items: "number"`
|
|
142
|
+
* where JSON Schema requires `items: {type: "number"}` — expand it
|
|
143
|
+
* (`"number"` → `{type: "number"}`, an unrecognized string → `{}`,
|
|
144
|
+
* the unconstrained schema). A boolean (`additionalProperties: false`)
|
|
145
|
+
* is a valid schema-position value and passes through; anything else
|
|
146
|
+
* recurses through {@link normalizeSchema}.
|
|
147
|
+
*/
|
|
148
|
+
function coerceToSchema(value) {
|
|
149
|
+
if (typeof value === 'string') {
|
|
150
|
+
const t = normalizeTypeValue(value, undefined);
|
|
151
|
+
return t === undefined ? {} : { type: t };
|
|
152
|
+
}
|
|
153
|
+
return normalizeSchema(value);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Recursively normalize a JSON Schema, repairing invalid `type`
|
|
157
|
+
* values. Non-object input (booleans, primitives) passes through —
|
|
158
|
+
* `additionalProperties: false` and the like are valid as-is.
|
|
159
|
+
*/
|
|
160
|
+
export function normalizeSchema(schema) {
|
|
161
|
+
if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) {
|
|
162
|
+
return schema;
|
|
163
|
+
}
|
|
164
|
+
const obj = schema;
|
|
165
|
+
const out = {};
|
|
166
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
167
|
+
if (key === 'type') {
|
|
168
|
+
const fixed = normalizeTypeValue(value, obj['enum']);
|
|
169
|
+
if (fixed !== undefined)
|
|
170
|
+
out[key] = fixed;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (SCHEMA_VALUED_KEYS.has(key)) {
|
|
174
|
+
out[key] = coerceToSchema(value);
|
|
175
|
+
}
|
|
176
|
+
else if (SCHEMA_MAP_KEYS.has(key)) {
|
|
177
|
+
out[key] = normalizeSchemaMap(value);
|
|
178
|
+
}
|
|
179
|
+
else if (SCHEMA_ARRAY_KEYS.has(key)) {
|
|
180
|
+
out[key] = Array.isArray(value)
|
|
181
|
+
? value.map((s) => coerceToSchema(s))
|
|
182
|
+
: value;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Data-value position (`default`, `const`, `enum`, `examples`,
|
|
186
|
+
// `required`, `description`, …) — leave verbatim.
|
|
187
|
+
out[key] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
package/dist/pure.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers — zero I/O, zero AWS dependencies.
|
|
3
|
+
*
|
|
4
|
+
* - `inferInteractionMode(text)` — keyword-priority mapper from free
|
|
5
|
+
* text (category / pattern / prompt) to a categorical interaction
|
|
6
|
+
* label. Used for telemetry / RAG hinting only; NOT part of the
|
|
7
|
+
* `DataContract` wire surface. Deterministic; default `'display'`.
|
|
8
|
+
* - `inferJsonSchemaType(tsType)` — TypeScript-style type-string
|
|
9
|
+
* mapper to the JSON Schema type union. Deterministic.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Categorical interaction-mode label used by `inferInteractionMode` for
|
|
13
|
+
* telemetry / RAG hinting. NOT a `DataContract` field — the four typed
|
|
14
|
+
* specs (propsSpec / actionSpec / contextSpec / streamSpec) describe
|
|
15
|
+
* the wire surface exhaustively, so interaction mode is a derived
|
|
16
|
+
* heuristic, never part of the contract.
|
|
17
|
+
*/
|
|
18
|
+
export type InteractionMode = 'display' | 'collect' | 'converse' | 'broadcast' | 'flow';
|
|
19
|
+
/**
|
|
20
|
+
* Infer interaction mode from any text (category, pattern, or prompt).
|
|
21
|
+
* Consolidated keyword matcher — all call sites use the same function.
|
|
22
|
+
*
|
|
23
|
+
* Used by rag-search and external benchmark runners for routing /
|
|
24
|
+
* telemetry. Deterministic; no I/O. Matches the first rule in priority
|
|
25
|
+
* order, defaulting to 'display'.
|
|
26
|
+
*/
|
|
27
|
+
export declare function inferInteractionMode(text: string): InteractionMode;
|
|
28
|
+
/** Map TypeScript-style type strings to JSON Schema types. */
|
|
29
|
+
export declare function inferJsonSchemaType(tsType: string): 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
30
|
+
//# sourceMappingURL=pure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pure.d.ts","sourceRoot":"","sources":["../src/pure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,CAAC;AAExF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAOlE;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAOxG"}
|
package/dist/pure.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers — zero I/O, zero AWS dependencies.
|
|
3
|
+
*
|
|
4
|
+
* - `inferInteractionMode(text)` — keyword-priority mapper from free
|
|
5
|
+
* text (category / pattern / prompt) to a categorical interaction
|
|
6
|
+
* label. Used for telemetry / RAG hinting only; NOT part of the
|
|
7
|
+
* `DataContract` wire surface. Deterministic; default `'display'`.
|
|
8
|
+
* - `inferJsonSchemaType(tsType)` — TypeScript-style type-string
|
|
9
|
+
* mapper to the JSON Schema type union. Deterministic.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Infer interaction mode from any text (category, pattern, or prompt).
|
|
13
|
+
* Consolidated keyword matcher — all call sites use the same function.
|
|
14
|
+
*
|
|
15
|
+
* Used by rag-search and external benchmark runners for routing /
|
|
16
|
+
* telemetry. Deterministic; no I/O. Matches the first rule in priority
|
|
17
|
+
* order, defaulting to 'display'.
|
|
18
|
+
*/
|
|
19
|
+
export function inferInteractionMode(text) {
|
|
20
|
+
const t = text.toLowerCase();
|
|
21
|
+
if (t.includes('form') || t.includes('survey') || t.includes('input') || t.includes('collect') || t.includes('sign up') || t.includes('contact'))
|
|
22
|
+
return 'collect';
|
|
23
|
+
if (t.includes('chat') || t.includes('conversation') || t.includes('messaging') || t.includes('talk'))
|
|
24
|
+
return 'converse';
|
|
25
|
+
if (t.includes('dashboard') || t.includes('monitor') || t.includes('live') || t.includes('feed') || t.includes('real-time'))
|
|
26
|
+
return 'broadcast';
|
|
27
|
+
if (t.includes('wizard') || t.includes('onboarding') || t.includes('checkout') || t.includes('stepper') || t.includes('multi-step') || t.includes('flow') || t.includes('step'))
|
|
28
|
+
return 'flow';
|
|
29
|
+
return 'display';
|
|
30
|
+
}
|
|
31
|
+
/** Map TypeScript-style type strings to JSON Schema types. */
|
|
32
|
+
export function inferJsonSchemaType(tsType) {
|
|
33
|
+
const t = tsType.toLowerCase();
|
|
34
|
+
if (t.includes('string'))
|
|
35
|
+
return 'string';
|
|
36
|
+
if (t.includes('number') || t.includes('int') || t.includes('float'))
|
|
37
|
+
return 'number';
|
|
38
|
+
if (t.includes('boolean') || t.includes('bool'))
|
|
39
|
+
return 'boolean';
|
|
40
|
+
if (t.includes('array') || t.includes('[]'))
|
|
41
|
+
return 'array';
|
|
42
|
+
return 'object';
|
|
43
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG Search — embedding-similarity blueprint retrieval over the
|
|
3
|
+
* public `@ggui-ai/mcp-server-core` storage seam.
|
|
4
|
+
*
|
|
5
|
+
* Given agent `prompt` + a tenant `scope` (typically appId, or the
|
|
6
|
+
* literal `"shared"` for the global catalog), this helper:
|
|
7
|
+
*
|
|
8
|
+
* 1. Embeds the prompt via {@link EmbeddingProvider.embed}.
|
|
9
|
+
* 2. Queries the {@link VectorStore} for the top-K nearest neighbors
|
|
10
|
+
* (server-side cosine, scope-partitioned — no cross-tenant leak).
|
|
11
|
+
* 3. Filters by minimum score, biases private (per-app registered
|
|
12
|
+
* UIs) over shared (generated pool) at the same score band, and
|
|
13
|
+
* projects each hit into a {@link NegotiatorOption}.
|
|
14
|
+
*
|
|
15
|
+
* Two pipeline paths emerge from the confidence band:
|
|
16
|
+
* - **High confidence** (`score >= HIGH_CONFIDENCE_THRESHOLD`): the
|
|
17
|
+
* hit is marked `exact`. Upstream callers (V3 `negotiate()`) can
|
|
18
|
+
* short-circuit the decision LLM entirely.
|
|
19
|
+
* - **Medium confidence** (`score >= RETRIEVAL_MIN_SCORE` and below
|
|
20
|
+
* the exact threshold): marked `partial`. Upstream passes these to
|
|
21
|
+
* the decision LLM as candidates.
|
|
22
|
+
*
|
|
23
|
+
* ### Public-seam contract
|
|
24
|
+
*
|
|
25
|
+
* This function reads only the public `VectorStore` surface (scalar
|
|
26
|
+
* metadata). Consumers that still hold the rich `EmbeddingStorage`
|
|
27
|
+
* shape bridge via
|
|
28
|
+
* `@ggui-cloud/aws-adapters.embeddingStorageToVectorStore`, which
|
|
29
|
+
* encodes array-valued fields (`props`, `callbacks`, `sourceTools`)
|
|
30
|
+
* as JSON strings inside `metadata`. Any `VectorStore` implementation
|
|
31
|
+
* that stores fresh writes through `writeRagVector` (see
|
|
32
|
+
* `mcp-servers/ggui-protocol/src/adapters/vector-store.ts`) uses the
|
|
33
|
+
* same encoding — so existing caches, fresh writes, and community
|
|
34
|
+
* `VectorStore` backends all collide on the same retrieval key.
|
|
35
|
+
*
|
|
36
|
+
* ### Scope
|
|
37
|
+
*
|
|
38
|
+
* No AWS bindings, no LLM dependency. Pure composition over the two
|
|
39
|
+
* public seams. The one-LLM-call `makeDecision()` step is separate.
|
|
40
|
+
*/
|
|
41
|
+
import type { EmbeddingProvider, VectorStore } from '@ggui-ai/mcp-server-core';
|
|
42
|
+
import type { NegotiatorOption } from './types.js';
|
|
43
|
+
/** RAG search result with per-stage timing. */
|
|
44
|
+
export interface RagSearchResult {
|
|
45
|
+
options: NegotiatorOption[];
|
|
46
|
+
embeddingLatencyMs: number;
|
|
47
|
+
searchLatencyMs: number;
|
|
48
|
+
}
|
|
49
|
+
/** Dependencies — both public seams from `@ggui-ai/mcp-server-core`. */
|
|
50
|
+
export interface RagSearchDeps {
|
|
51
|
+
embedding: EmbeddingProvider;
|
|
52
|
+
vectors: VectorStore;
|
|
53
|
+
}
|
|
54
|
+
export interface RagSearchInput {
|
|
55
|
+
/** Natural-language query text. Embedded as-is. */
|
|
56
|
+
prompt: string;
|
|
57
|
+
/**
|
|
58
|
+
* Scope / tenant partition. Typically `appId` for per-app indexes
|
|
59
|
+
* or the literal `"shared"` for the global catalog. Passed straight
|
|
60
|
+
* through to `VectorStore.query`; the scope is the tenant boundary,
|
|
61
|
+
* so a query never crosses into another tenant's index.
|
|
62
|
+
*/
|
|
63
|
+
scope: string;
|
|
64
|
+
/** Override the k-NN cut-off (default 10). */
|
|
65
|
+
maxCandidates?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Search the vector index for blueprints matching `prompt` within
|
|
69
|
+
* `scope`. See the module docstring for the confidence-band pipeline
|
|
70
|
+
* and encoding contract.
|
|
71
|
+
*/
|
|
72
|
+
export declare function ragSearch(deps: RagSearchDeps, input: RagSearchInput): Promise<RagSearchResult>;
|
|
73
|
+
//# sourceMappingURL=rag-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rag-search.d.ts","sourceRoot":"","sources":["../src/rag-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EACV,iBAAiB,EAEjB,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,iBAAiB,CAAC;IAC7B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAWD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,eAAe,CAAC,CAwC1B"}
|