@ggui-ai/negotiator 0.2.0-alpha.4 → 0.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +25 -19
  2. package/dist/contract-validators.d.ts +20 -16
  3. package/dist/contract-validators.d.ts.map +1 -1
  4. package/dist/contract-validators.js +11 -8
  5. package/dist/index.d.ts +20 -20
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +20 -14
  8. package/dist/llm-caller.d.ts +9 -9
  9. package/dist/llm-caller.d.ts.map +1 -1
  10. package/dist/llm-caller.js +8 -8
  11. package/dist/llm-rerank.d.ts +9 -8
  12. package/dist/llm-rerank.d.ts.map +1 -1
  13. package/dist/llm-rerank.js +4 -25
  14. package/dist/normalize-draft.d.ts.map +1 -1
  15. package/dist/normalize-draft.js +33 -9
  16. package/dist/normalize-schema.d.ts.map +1 -1
  17. package/dist/normalize-schema.js +12 -0
  18. package/dist/synth-bench/cache-key-probe.d.ts +3 -0
  19. package/dist/synth-bench/cache-key-probe.d.ts.map +1 -0
  20. package/dist/synth-bench/cache-key-probe.js +177 -0
  21. package/dist/synth-bench/corpus.d.ts.map +1 -1
  22. package/dist/synth-bench/corpus.js +28 -20
  23. package/dist/synth-bench/run-bench.js +2 -2
  24. package/dist/synthesize-contract.d.ts +2 -3
  25. package/dist/synthesize-contract.d.ts.map +1 -1
  26. package/dist/synthesize-contract.js +20 -15
  27. package/package.json +8 -7
  28. package/src/contract-validators.ts +21 -17
  29. package/src/index.ts +20 -33
  30. package/src/llm-caller.ts +9 -9
  31. package/src/llm-rerank.ts +12 -17
  32. package/src/normalize-draft.ts +35 -9
  33. package/src/normalize-schema.ts +11 -0
  34. package/src/synth-bench/cache-key-probe.ts +213 -0
  35. package/src/synth-bench/corpus.ts +28 -20
  36. package/src/synth-bench/run-bench.ts +2 -2
  37. package/src/synthesize-contract.ts +24 -20
  38. package/dist/decision-input.d.ts +0 -51
  39. package/dist/decision-input.d.ts.map +0 -1
  40. package/dist/decision-input.js +0 -17
  41. package/dist/decision.d.ts +0 -58
  42. package/dist/decision.d.ts.map +0 -1
  43. package/dist/decision.js +0 -501
  44. package/dist/intent.d.ts +0 -22
  45. package/dist/intent.d.ts.map +0 -1
  46. package/dist/intent.js +0 -28
  47. package/dist/negotiate.d.ts +0 -142
  48. package/dist/negotiate.d.ts.map +0 -1
  49. package/dist/negotiate.js +0 -163
  50. package/dist/pure.d.ts +0 -30
  51. package/dist/pure.d.ts.map +0 -1
  52. package/dist/pure.js +0 -43
  53. package/dist/rag-search.d.ts +0 -73
  54. package/dist/rag-search.d.ts.map +0 -1
  55. package/dist/rag-search.js +0 -192
  56. package/dist/render.d.ts +0 -57
  57. package/dist/render.d.ts.map +0 -1
  58. package/dist/render.js +0 -25
  59. package/dist/suggestion.d.ts +0 -38
  60. package/dist/suggestion.d.ts.map +0 -1
  61. package/dist/suggestion.js +0 -47
  62. package/dist/types.d.ts +0 -30
  63. package/dist/types.d.ts.map +0 -1
  64. package/dist/types.js +0 -13
  65. package/src/decision-input.ts +0 -52
  66. package/src/decision.ts +0 -581
  67. package/src/intent.ts +0 -37
  68. package/src/negotiate.ts +0 -314
  69. package/src/pure.ts +0 -46
  70. package/src/rag-search.ts +0 -274
  71. package/src/render.ts +0 -56
  72. package/src/suggestion.ts +0 -73
  73. package/src/types.ts +0 -31
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Blueprint cache-key stability probe.
4
+ *
5
+ * The handshake blueprint cache hits ONLY on exact canonical-contract
6
+ * equality: `blueprintKey(contract)` = sha256 of `canonicalizeContracts`
7
+ * (RFC 8785), which preserves spec placement, action/slot/tool NAMES and
8
+ * schema shapes (it only normalizes key order / whitespace and strips
9
+ * `description`/`usage`). The semantic/fuzzy match is disabled whenever a
10
+ * contract is present. So two requests for the SAME intent reuse a cached
11
+ * blueprint only if the LLM authored a byte-identical canonical contract
12
+ * both times.
13
+ *
14
+ * This probe synthesizes one intent N times and reports:
15
+ * 1. how many DISTINCT blueprintKeys result (distinct > 1 ⇒ the
16
+ * exact-key cache structurally cannot hit on re-runs);
17
+ * 2. the KEY-AFFECTING diff — every leaf path where the runs disagree
18
+ * (descriptions/usage stripped, since the key ignores them) — so we
19
+ * can see WHETHER the variance is trivial (normalizable: required
20
+ * order, additionalProperties, optional fields) or semantic
21
+ * (property names / structure → only a semantic match can bridge it).
22
+ *
23
+ * Usage:
24
+ * pnpm -F @ggui-ai/negotiator cache-probe -- --intent "my todo items" --n 5
25
+ * pnpm -F @ggui-ai/negotiator cache-probe -- --n 5 --dump /tmp/cache-probe.json
26
+ */
27
+ import { writeFileSync } from 'node:fs';
28
+ import { blueprintKey } from '@ggui-ai/protocol/blueprint-key';
29
+ import { synthesizeContract } from '../synthesize-contract.js';
30
+ import { DEFAULT_MODEL, buildAnthropicLlmCaller, resolveAnthropicKey, } from './cli-llm.js';
31
+ function parseArgs(argv) {
32
+ let intent = 'my todo items';
33
+ let n = 3;
34
+ let model = DEFAULT_MODEL;
35
+ let dump;
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const a = argv[i];
38
+ if (a === '--intent' && argv[i + 1])
39
+ intent = argv[++i];
40
+ else if (a === '--n' && argv[i + 1])
41
+ n = Number(argv[++i]);
42
+ else if (a === '--model' && argv[i + 1])
43
+ model = argv[++i];
44
+ else if (a === '--dump' && argv[i + 1])
45
+ dump = argv[++i];
46
+ }
47
+ return { intent, n, model, ...(dump !== undefined ? { dump } : {}) };
48
+ }
49
+ /** One-line canonical-surface summary — the bits blueprintKey is sensitive to. */
50
+ function shapeSummary(c) {
51
+ const parts = [];
52
+ const props = Object.keys(c.propsSpec?.properties ?? {});
53
+ const ctx = Object.keys(c.contextSpec ?? {});
54
+ const act = Object.keys(c.actionSpec ?? {});
55
+ const str = Object.keys(c.streamSpec ?? {});
56
+ const tools = Object.keys(c.agentCapabilities?.tools ?? {});
57
+ if (props.length)
58
+ parts.push(`props{${props.join(',')}}`);
59
+ if (ctx.length)
60
+ parts.push(`context{${ctx.join(',')}}`);
61
+ if (act.length)
62
+ parts.push(`action{${act.join(',')}}`);
63
+ if (str.length)
64
+ parts.push(`stream{${str.join(',')}}`);
65
+ if (tools.length)
66
+ parts.push(`tools{${tools.join(',')}}`);
67
+ return parts.join(' ') || '(empty)';
68
+ }
69
+ /**
70
+ * Flatten a contract to `path → json-encoded leaf`, applying the same
71
+ * domain rule the cache key does: drop string-valued `description` /
72
+ * `usage` (informational — `canonicalizeContracts` strips them). Objects
73
+ * recurse; arrays are encoded whole (order is key-affecting under JCS,
74
+ * which sorts object keys but NOT array elements).
75
+ */
76
+ function flattenKeyAffecting(value, prefix, out) {
77
+ if (Array.isArray(value)) {
78
+ out.set(prefix, JSON.stringify(value));
79
+ return;
80
+ }
81
+ if (value !== null && typeof value === 'object') {
82
+ for (const [k, v] of Object.entries(value)) {
83
+ if ((k === 'description' || k === 'usage') && typeof v === 'string') {
84
+ continue; // stripped by the cache key
85
+ }
86
+ flattenKeyAffecting(v, prefix ? `${prefix}.${k}` : k, out);
87
+ }
88
+ return;
89
+ }
90
+ out.set(prefix, JSON.stringify(value));
91
+ }
92
+ /** Every leaf path where the N contracts do not all agree, with the
93
+ * distinct per-run values (or `∅` when a run omits the path). */
94
+ function crossRunDiff(contracts) {
95
+ const flat = contracts.map((c) => {
96
+ const m = new Map();
97
+ flattenKeyAffecting(c, '', m);
98
+ return m;
99
+ });
100
+ const allPaths = new Set();
101
+ for (const m of flat)
102
+ for (const p of m.keys())
103
+ allPaths.add(p);
104
+ const divergent = [];
105
+ for (const path of [...allPaths].sort()) {
106
+ const values = flat.map((m) => m.get(path) ?? '∅');
107
+ if (new Set(values).size > 1)
108
+ divergent.push({ path, values });
109
+ }
110
+ return divergent;
111
+ }
112
+ async function main() {
113
+ const args = parseArgs(process.argv.slice(2));
114
+ const llm = buildAnthropicLlmCaller(resolveAnthropicKey('cache-probe'), args.model);
115
+ process.stdout.write(`cache-probe: intent=${JSON.stringify(args.intent)} n=${args.n} model=${args.model}\n\n`);
116
+ const contracts = [];
117
+ const keys = [];
118
+ for (let i = 0; i < args.n; i++) {
119
+ const synth = await synthesizeContract({ llm }, args.intent);
120
+ if (synth.contract === null) {
121
+ process.stdout.write(` run ${i + 1}: synth declined — ${synth.reason}\n`);
122
+ continue;
123
+ }
124
+ const key = blueprintKey(synth.contract);
125
+ contracts.push(synth.contract);
126
+ keys.push(key);
127
+ process.stdout.write(` run ${i + 1}: ${key} ${shapeSummary(synth.contract)}\n`);
128
+ }
129
+ const distinct = new Set(keys);
130
+ process.stdout.write(`\nDistinct blueprintKeys: ${distinct.size}/${keys.length}\n`);
131
+ if (distinct.size <= 1 && keys.length > 1) {
132
+ process.stdout.write('STABLE → the exact-key cache WOULD hit on a re-run of this intent.\n');
133
+ }
134
+ else if (keys.length > 1) {
135
+ process.stdout.write('UNSTABLE → same intent yields different canonical contracts → the exact-key\n' +
136
+ 'cache cannot hit; each request cold-gens a new blueprint.\n');
137
+ }
138
+ // Key-affecting cross-run diff — the "what varies" the user asked for.
139
+ if (contracts.length > 1) {
140
+ const diff = crossRunDiff(contracts);
141
+ process.stdout.write(`\n=== KEY-AFFECTING DIFF (${diff.length} divergent paths; descriptions stripped) ===\n`);
142
+ for (const { path, values } of diff) {
143
+ process.stdout.write(` ${path}\n`);
144
+ values.forEach((v, i) => {
145
+ const capped = v.length > 100 ? `${v.slice(0, 100)}…` : v;
146
+ process.stdout.write(` run${i + 1}: ${capped}\n`);
147
+ });
148
+ }
149
+ // Heuristic taxonomy hint.
150
+ const isArrayOrderOnly = diff.every(({ values }) => {
151
+ const parsed = values
152
+ .filter((v) => v !== '∅')
153
+ .map((v) => {
154
+ try {
155
+ return JSON.stringify([...JSON.parse(v)].sort());
156
+ }
157
+ catch {
158
+ return v;
159
+ }
160
+ });
161
+ return new Set(parsed).size === 1;
162
+ });
163
+ process.stdout.write(`\nHint: ${diff.length === 0
164
+ ? 'no key-affecting differences — variance must be elsewhere'
165
+ : isArrayOrderOnly
166
+ ? 'ALL divergent paths are arrays differing only in ORDER → a canonical sort in canonicalizeContracts would collapse these keys (cheap fix, no semantic match needed).'
167
+ : 'divergent paths include non-array-order differences (names / types / structure / presence) → a semantic intent-match is needed to bridge them (canonicalization alone cannot).'}\n`);
168
+ }
169
+ if (args.dump !== undefined) {
170
+ writeFileSync(args.dump, JSON.stringify({ intent: args.intent, model: args.model, keys, contracts }, null, 2));
171
+ process.stdout.write(`\nDumped ${contracts.length} contracts → ${args.dump}\n`);
172
+ }
173
+ }
174
+ main().catch((err) => {
175
+ process.stderr.write(`cache-probe failed: ${err instanceof Error ? err.message : String(err)}\n`);
176
+ process.exit(1);
177
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../src/synth-bench/corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;+DAG2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;2DAIuD;IACvD,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C;;;0EAGsE;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;;;;;;OASG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD;;;;;sEAKkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C;;qDAEiD;IACjD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAOhE;AAoDD,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAs8B7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,UAAU,EA8Q9C,CAAC"}
1
+ {"version":3,"file":"corpus.d.ts","sourceRoot":"","sources":["../../src/synth-bench/corpus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,CAAC;IAC1C;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;+DAG2D;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;2DAIuD;IACvD,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C;;;0EAGsE;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;;;;;;;;;OASG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD;;;;;sEAKkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C;;qDAEiD;IACjD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAOhE;AAoDD,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAs8B7C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,UAAU,EAsR9C,CAAC"}
@@ -1091,19 +1091,23 @@ export const REPAIR_CORPUS = [
1091
1091
  agentCapabilities: {
1092
1092
  tools: {
1093
1093
  todo_toggle: {
1094
- description: 'Flip a todo done/undone',
1095
- inputSchema: {
1096
- type: 'object',
1097
- properties: { id: { type: 'string' } },
1098
- required: ['id'],
1094
+ toolInfo: {
1095
+ description: 'Flip a todo done/undone',
1096
+ inputSchema: {
1097
+ type: 'object',
1098
+ properties: { id: { type: 'string' } },
1099
+ required: ['id'],
1100
+ },
1099
1101
  },
1100
1102
  },
1101
1103
  todo_add: {
1102
- description: 'Append a new todo',
1103
- inputSchema: {
1104
- type: 'object',
1105
- properties: { text: { type: 'string' } },
1106
- required: ['text'],
1104
+ toolInfo: {
1105
+ description: 'Append a new todo',
1106
+ inputSchema: {
1107
+ type: 'object',
1108
+ properties: { text: { type: 'string' } },
1109
+ required: ['text'],
1110
+ },
1107
1111
  },
1108
1112
  },
1109
1113
  },
@@ -1178,19 +1182,23 @@ export const REPAIR_CORPUS = [
1178
1182
  agentCapabilities: {
1179
1183
  tools: {
1180
1184
  todo_toggle: {
1181
- description: 'Flip a todo done/undone',
1182
- inputSchema: {
1183
- type: 'object',
1184
- properties: { id: { type: 'string' } },
1185
- required: ['id'],
1185
+ toolInfo: {
1186
+ description: 'Flip a todo done/undone',
1187
+ inputSchema: {
1188
+ type: 'object',
1189
+ properties: { id: { type: 'string' } },
1190
+ required: ['id'],
1191
+ },
1186
1192
  },
1187
1193
  },
1188
1194
  todo_add: {
1189
- description: 'Append a new todo',
1190
- inputSchema: {
1191
- type: 'object',
1192
- properties: { text: { type: 'string' } },
1193
- required: ['text'],
1195
+ toolInfo: {
1196
+ description: 'Append a new todo',
1197
+ inputSchema: {
1198
+ type: 'object',
1199
+ properties: { text: { type: 'string' } },
1200
+ required: ['text'],
1201
+ },
1194
1202
  },
1195
1203
  },
1196
1204
  },
@@ -34,7 +34,7 @@
34
34
  */
35
35
  import { listContractGadgets } from '@ggui-ai/protocol';
36
36
  import { synthesizeContract } from '../synthesize-contract.js';
37
- import { validateContractStructure, } from '../contract-validators.js';
37
+ import { validateContractRedundancy, } from '../contract-validators.js';
38
38
  import { BENCH_CORPUS, contractShape, } from './corpus.js';
39
39
  /**
40
40
  * Failure kinds that are ADVISORY — surfaced in the report but do NOT
@@ -246,7 +246,7 @@ export async function evaluateAgainstCorpus(deps, options = {}, corpus = BENCH_C
246
246
  };
247
247
  }
248
248
  else {
249
- const findings = validateContractStructure(synth.contract).findings;
249
+ const findings = validateContractRedundancy(synth.contract).findings;
250
250
  const score = scoreSynthesizedContract(synth.contract, entry.expected);
251
251
  outcome = {
252
252
  entry,
@@ -56,7 +56,7 @@ export interface SynthesizeContractResult {
56
56
  readonly attempts: number;
57
57
  /**
58
58
  * Structural-validator findings produced when the synthesizer ran the
59
- * `validateContractStructure` gate against its assembled contract.
59
+ * `validateContractRedundancy` gate against its assembled contract.
60
60
  * Empty when validator didn't run (early skip / decline). Surfaced
61
61
  * so callers (cache-trace emit site, ops dashboards) can render
62
62
  * findings without re-running the detector.
@@ -136,8 +136,7 @@ export declare function synthesizeContract(deps: {
136
136
  }): Promise<SynthesizeContractResult>;
137
137
  /**
138
138
  * Compose the "AVAILABLE GADGETS" section appended to synth's user
139
- * prompt (and the decision-engine user message — both paths share
140
- * this one composer). Flattens the package-keyed
139
+ * prompt. Flattens the package-keyed
141
140
  * {@link GadgetDescriptor} catalog into one line per export:
142
141
  *
143
142
  * - hook `useGeolocation` (package `@ggui-ai/gadgets`) — <desc> (usage: <usage>)
@@ -1 +1 @@
1
- {"version":3,"file":"synthesize-contract.d.ts","sourceRoot":"","sources":["../src/synthesize-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAM7D,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,0BAA0B,CAAC;AAsBlC,uCAAuC;AACvC,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IACvC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACzD;AAuOD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,UA+I7B,CAAC;AAwRF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;CAAE,EACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS;QAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;CACL,GACA,OAAO,CAAC,wBAAwB,CAAC,CAyMnC;AAgUD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,GAC/C,MAAM,GAAG,SAAS,CA+CpB"}
1
+ {"version":3,"file":"synthesize-contract.d.ts","sourceRoot":"","sources":["../src/synthesize-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,gBAAgB,EAEtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAM7D,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,0BAA0B,CAAC;AAsBlC,uCAAuC;AACvC,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IACvC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,yBAAyB,EAAE,CAAC;CACzD;AAuOD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,UA+I7B,CAAC;AAwRF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;CAAE,EACjC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IACR;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAClD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS;QAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;CACL,GACA,OAAO,CAAC,wBAAwB,CAAC,CAyMnC;AAmUD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,GAC/C,MAAM,GAAG,SAAS,CA+CpB"}
@@ -40,7 +40,7 @@ import { dataContractSchema, gadgetExportName, } from '@ggui-ai/protocol';
40
40
  import { lintContract } from '@ggui-ai/protocol';
41
41
  import { normalizeSchema } from './normalize-schema.js';
42
42
  import { draftSeedPropKeys, findDroppedSeedSurfaces, } from './preserve-seed-surfaces.js';
43
- import { formatValidationFindings, validateActionsVsContext, validateContractCoherence, validateContractStructure, } from './contract-validators.js';
43
+ import { formatValidationFindings, validateActionsVsContext, validateContractCoherence, validateContractRedundancy, } from './contract-validators.js';
44
44
  /**
45
45
  * Map a protocol-linter {@link ContractIssue} into the negotiator's
46
46
  * existing {@link ContractValidationFinding} shape so phase-2/3/4
@@ -257,7 +257,7 @@ Several retired field/hook names appear in older training data. The cross-ref li
257
257
  - clientCapabilities.capabilities (retired inner key; use clientCapabilities.gadgets)
258
258
  - useWiredTool(...) / useClientTool(...) / useAgentTool(...) (retired hooks; the contract layer doesn't reference these at all)
259
259
  - "@ggui-ai/client-tools" as a package import (retired package; use @ggui-ai/gadgets)
260
- - PushStory / story.* on handshake input (retired wire shape; handshake input is flat — {renderId, intent, contract?, hint?, forceCreate?})
260
+ - PushStory / story.* on handshake input (retired wire shape; handshake input is flat — {sessionId, intent, contract?, hint?, forceCreate?})
261
261
  - story.adapters / declaredAdapters (retired adapter gate; permissions are a registry-side descriptor field — the wire clientCapabilities.gadgets map carries identity only)
262
262
  - dispatch: {kind: 'tool', tool: ...} (retired discriminated union on actionSpec entries — actions carry NO tool wiring)
263
263
  - dispatch: {kind: 'agent', intendedTool: ...} (retired — same; do not wire actions to tools)
@@ -622,7 +622,7 @@ function pruneRedundantActions(contract) {
622
622
  if (actionSpec === undefined)
623
623
  return contract;
624
624
  const redundant = new Set();
625
- for (const f of validateContractStructure(contract).findings) {
625
+ for (const f of validateContractRedundancy(contract).findings) {
626
626
  if (f.kind === 'redundant-action' && typeof f.actionName === 'string') {
627
627
  redundant.add(f.actionName);
628
628
  }
@@ -764,7 +764,7 @@ export async function synthesizeContract(deps, intent, options) {
764
764
  // get it right every draw. Subtractive, so the contract stays
765
765
  // schema-valid.
766
766
  const validatedContract = pruneRedundantActions(validated.data);
767
- const structureSafety = validateContractStructure(validatedContract);
767
+ const structureSafety = validateContractRedundancy(validatedContract);
768
768
  const placementSafety = validateActionsVsContext(validatedContract);
769
769
  // Intent-aware coherence — the one validator that reads `trimmed`.
770
770
  // Catches the degenerate "actionSpec-only, no data surface"
@@ -1015,20 +1015,26 @@ function buildContract(input) {
1015
1015
  for (const [name, entry] of Object.entries(input.agentCapabilities.tools)) {
1016
1016
  if (!entry || typeof entry !== 'object')
1017
1017
  continue;
1018
+ // The LLM authors the ergonomic flat shape ({inputSchema?,
1019
+ // outputSchema?, usage?}); the wire shape nests the MCP descriptor
1020
+ // under `toolInfo` with a REQUIRED `inputSchema` (default the
1021
+ // void-object schema when the LLM omitted it). `usage` stays on the
1022
+ // ggui authoring layer alongside the descriptor.
1023
+ const inputSchema = (entry.inputSchema !== undefined
1024
+ ? normalizeSchema(entry.inputSchema)
1025
+ : { type: 'object' });
1018
1026
  built[name] = {
1019
- ...(entry.inputSchema !== undefined
1020
- ? { inputSchema: normalizeSchema(entry.inputSchema) }
1021
- : {}),
1022
- ...(entry.outputSchema !== undefined
1023
- ? { outputSchema: normalizeSchema(entry.outputSchema) }
1024
- : {}),
1027
+ toolInfo: {
1028
+ inputSchema,
1029
+ ...(entry.outputSchema !== undefined
1030
+ ? { outputSchema: normalizeSchema(entry.outputSchema) }
1031
+ : {}),
1032
+ },
1025
1033
  ...(typeof entry.usage === 'string' ? { usage: entry.usage } : {}),
1026
1034
  };
1027
1035
  }
1028
1036
  if (Object.keys(built).length > 0) {
1029
- contract.agentCapabilities = {
1030
- tools: built,
1031
- };
1037
+ contract.agentCapabilities = { tools: built };
1032
1038
  }
1033
1039
  }
1034
1040
  // clientCapabilities.gadgets — browser-capability gadgets the
@@ -1090,8 +1096,7 @@ const SYNTH_PER_LIBRARY_BUDGET = 300;
1090
1096
  const SYNTH_TOTAL_BUDGET = 3_000;
1091
1097
  /**
1092
1098
  * Compose the "AVAILABLE GADGETS" section appended to synth's user
1093
- * prompt (and the decision-engine user message — both paths share
1094
- * this one composer). Flattens the package-keyed
1099
+ * prompt. Flattens the package-keyed
1095
1100
  * {@link GadgetDescriptor} catalog into one line per export:
1096
1101
  *
1097
1102
  * - hook `useGeolocation` (package `@ggui-ai/gadgets`) — <desc> (usage: <usage>)
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@ggui-ai/negotiator",
3
- "version": "0.2.0-alpha.4",
4
- "description": "UI decision engine for ggui. Given an agent's signal and the current render state, decides which UI to render (create / update / replace) and synthesizes the data contract. Deployment-agnostic: concrete embedding and vector-store bindings plug in via the storage interfaces from @ggui-ai/mcp-server-core.",
3
+ "version": "0.3.0-rc.0",
4
+ "description": "Contract-synthesis + match-judge engine for ggui's handshake. Synthesizes or repairs a conforming DataContract from an agent's draft, judges blueprint-match candidates for reuse, and validates contract structure + novelty — the primitives composed by decideHandshake in @ggui-ai/mcp-server-handlers. Deployment-agnostic: concrete embedding and vector-store bindings plug in via the storage interfaces from @ggui-ai/mcp-server-core.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
7
7
  "ggui",
8
8
  "mcp",
9
9
  "generative-ui",
10
10
  "ai-agents",
11
- "decision-engine"
11
+ "contract-synthesis"
12
12
  ],
13
13
  "homepage": "https://github.com/ggui-ai/ggui/tree/main/packages/negotiator",
14
14
  "repository": {
@@ -47,8 +47,8 @@
47
47
  }
48
48
  },
49
49
  "dependencies": {
50
- "@ggui-ai/mcp-server-core": "0.2.0-alpha.4",
51
- "@ggui-ai/protocol": "0.2.0-alpha.4"
50
+ "@ggui-ai/protocol": "0.3.0-rc.0",
51
+ "@ggui-ai/mcp-server-core": "0.3.0-rc.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^24.0.0",
@@ -63,13 +63,14 @@
63
63
  },
64
64
  "author": "ggui contributors <hello@ggui.ai>",
65
65
  "scripts": {
66
- "build": "tsc -p tsconfig.build.json",
66
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
67
67
  "dev": "tsc --watch",
68
68
  "typecheck": "tsc --noEmit",
69
69
  "test": "vitest run",
70
70
  "test:watch": "vitest",
71
71
  "probe-rerank": "tsx src/rerank-eval/run-probe-cli.ts",
72
72
  "bench-synth": "tsx src/synth-bench/run-bench-cli.ts",
73
- "bench-repair": "tsx src/synth-bench/run-repair-bench-cli.ts"
73
+ "bench-repair": "tsx src/synth-bench/run-repair-bench-cli.ts",
74
+ "cache-probe": "tsx src/synth-bench/cache-key-probe.ts"
74
75
  }
75
76
  }
@@ -3,9 +3,12 @@
3
3
  *
4
4
  * Two detectors live here:
5
5
  *
6
- * - {@link validateContractStructure} — pure structural heuristics that
7
- * flag over-specified contracts without any runtime dependency. The
8
- * load-bearing finding is `redundant-action`: an empty-payload
6
+ * - {@link validateContractRedundancy} — pure redundancy heuristics that
7
+ * flag over-specified contracts without any runtime dependency.
8
+ * (Deliberately NOT named `validateContractStructure` — that name is
9
+ * owned by `@ggui-ai/protocol`'s normative structural validator,
10
+ * which returns `ContractViolation`s; this one returns advisory
11
+ * findings.) The load-bearing finding is `redundant-action`: an empty-payload
9
12
  * `actionSpec` entry whose name parses as a mutator of an existing
10
13
  * `contextSpec` slot. Real example that motivated this module: a
11
14
  * synthesizer emitted both `actionSpec.increment` (empty payload)
@@ -80,26 +83,27 @@ export interface ContractValidationResult {
80
83
 
81
84
  /**
82
85
  * Dependencies the novelty detector needs. Embedding provider + vector
83
- * store are the same seams the negotiator's RAG path uses, so the
84
- * novelty check operates over the production index without any extra
85
- * infrastructure.
86
+ * store are the same seams the handshake's find-similar retrieval uses,
87
+ * so the novelty check operates over the production index without any
88
+ * extra infrastructure.
86
89
  */
87
90
  export interface ContractValidationNoveltyDeps {
88
91
  readonly embedding: EmbeddingProvider;
89
92
  readonly vectorStore: VectorStore;
90
93
  /** Tenant / partition the nearest-neighbor query runs against. Same
91
- * semantics as `RagSearchInput.scope`. */
94
+ * semantics as the `scope` argument on `VectorStore.query` — the
95
+ * tenant / index partition, typically `appId`. */
92
96
  readonly scope: string;
93
97
  }
94
98
 
95
99
  export interface ContractValidationNoveltyOptions {
96
100
  /**
97
101
  * Cosine distance threshold above which the contract is flagged as
98
- * novel. Distance is `1 - cosine_similarity`. Default `0.8` —
99
- * matches the negotiator's `RETRIEVAL_MIN_SCORE = 0.15` (=cosine
100
- * similarity 0.15, distance 0.85) one-tail boundary, with a small
101
- * buffer so contracts that hover near retrieval but slightly above
102
- * still flag for review.
102
+ * novel. Distance is `1 - cosine_similarity`. Default `0.8`: a
103
+ * contract whose nearest stored neighbor sits beyond distance 0.8
104
+ * is effectively outside retrieval range, with a small buffer below
105
+ * the hard one-tail boundary (distance 0.85) so contracts that
106
+ * hover near retrieval but slightly above still flag for review.
103
107
  */
104
108
  readonly thresholdCosine?: number;
105
109
  }
@@ -251,20 +255,20 @@ function isEmptyPayloadSchema(schema: JsonSchema | undefined): boolean {
251
255
  }
252
256
 
253
257
  // =============================================================================
254
- // Structural validator (synchronous, dependency-free)
258
+ // Redundancy validator (synchronous, dependency-free)
255
259
  // =============================================================================
256
260
 
257
261
  /**
258
- * Run the synchronous structural detectors against `contract`.
262
+ * Run the synchronous redundancy detector against `contract`.
259
263
  *
260
- * Currently:
264
+ * One finding kind:
261
265
  * - `redundant-action`: empty-payload action whose name parses as a
262
266
  * mutator of an existing context slot.
263
267
  *
264
- * Returns an empty findings array for contracts that don't trip any
268
+ * Returns an empty findings array for contracts that don't trip the
265
269
  * heuristic.
266
270
  */
267
- export function validateContractStructure(
271
+ export function validateContractRedundancy(
268
272
  contract: DataContract,
269
273
  ): ContractValidationResult {
270
274
  const findings: ContractValidationFinding[] = [];
package/src/index.ts CHANGED
@@ -1,46 +1,32 @@
1
1
  /**
2
- * @ggui-ai/negotiator — open-source UI decision engine for ggui.
2
+ * @ggui-ai/negotiator — open-source contract-synthesis + match-judge
3
+ * engine for ggui's handshake.
3
4
  *
4
- * Decides which UI to render (create/update/replace) given agent
5
- * signal (data/prompt/context/agentTools) and current render state.
5
+ * Given an agent's draft contract + intent, this package:
6
+ * - synthesizes / repairs a conforming `DataContract`
7
+ * (`synthesizeContract`, `ensureConformingContract`) so the
8
+ * handshake always returns a valid contract;
9
+ * - judges blueprint-match candidates for reuse (`rerankCandidates`);
10
+ * - validates contract structure + novelty (`contract-validators`);
11
+ * - hashes contracts into identity + variant keys (`hashContract`,
12
+ * `buildVariant`) and normalizes untrusted drafts (`normalizeDraft`).
13
+ *
14
+ * The HANDSHAKE DECISION itself (find-similar → reuse vs synth-create)
15
+ * lives in the shared `decideHandshake` core in
16
+ * `@ggui-ai/mcp-server-handlers`, which composes these primitives; the
17
+ * former in-package `negotiate()` RAG+decision pipeline was retired in
18
+ * favor of that unified, adapter-injected core.
6
19
  *
7
20
  * Composes the storage seams defined in `@ggui-ai/mcp-server-core`
8
- * (`EmbeddingProvider`, `VectorStore`, `Negotiator`). The decision
9
- * semantics are open here; concrete cloud-vendor bindings (e.g. a
10
- * managed embedding service or vector store) live behind those seams
11
- * so this package stays deployment-agnostic.
21
+ * (`EmbeddingProvider`, `VectorStore`) so this package stays
22
+ * deployment-agnostic.
12
23
  *
13
24
  * This barrel stays narrow — it exports only the minimum surface
14
25
  * consumers need. Each additive export carries semver weight.
15
26
  */
16
27
 
17
28
  export { hashContract, buildVariant } from './contract-hash.js';
18
- export { computeIntentId, shouldSuppressSuggestion } from './intent.js';
19
- export { detectDataPatterns, buildSuggestion } from './suggestion.js';
20
- export type { NegotiatorSuggestion } from './suggestion.js';
21
- export { inferInteractionMode, inferJsonSchemaType } from './pure.js';
22
- export { ragSearch } from './rag-search.js';
23
- export type {
24
- RagSearchDeps,
25
- RagSearchInput,
26
- RagSearchResult,
27
- } from './rag-search.js';
28
- export type { NegotiatorOption } from './types.js';
29
29
  export type { LLMCaller, LLMCallerConfig, ToolSchema } from './llm-caller.js';
30
- export type { RenderState, RenderEntry } from './render.js';
31
- export type { NegotiatorDecisionInput } from './decision-input.js';
32
- export {
33
- DECISION_SYSTEM_PROMPT,
34
- buildDecisionUserMessage,
35
- makeDecision,
36
- } from './decision.js';
37
- export { negotiate } from './negotiate.js';
38
- export type {
39
- NegotiateDeps,
40
- NegotiateInput,
41
- NegotiateConfig,
42
- NegotiateResult,
43
- } from './negotiate.js';
44
30
  export { rerankCandidates } from './llm-rerank.js';
45
31
  export type {
46
32
  RerankCandidate,
@@ -51,8 +37,9 @@ export { synthesizeContract } from './synthesize-contract.js';
51
37
  export type { SynthesizeContractResult } from './synthesize-contract.js';
52
38
  export { ensureConformingContract } from './ensure-conforming-contract.js';
53
39
  export type { EnsureConformingResult } from './ensure-conforming-contract.js';
40
+ export { normalizeDraft } from './normalize-draft.js';
54
41
  export {
55
- validateContractStructure,
42
+ validateContractRedundancy,
56
43
  validateContractNovelty,
57
44
  formatValidationFindings,
58
45
  } from './contract-validators.js';
package/src/llm-caller.ts CHANGED
@@ -1,23 +1,23 @@
1
1
  /**
2
- * `LLMCaller` — the decision engine's LLM dispatcher.
2
+ * `LLMCaller` — the negotiator's LLM dispatcher.
3
3
  *
4
4
  * Narrow abstraction over "call a chat model, optionally with a forced
5
5
  * tool-use schema for guaranteed-JSON structured output." Kept public
6
6
  * so OSS consumers of `@ggui-ai/negotiator` can bring their own LLM
7
7
  * provider (Anthropic direct, OpenAI, Google, a local model, a
8
- * community LiteLLM wrapper) without touching the decision-engine
8
+ * community LiteLLM wrapper) without touching the synthesis / judge
9
9
  * source.
10
10
  *
11
11
  * **Why this lives in `@ggui-ai/negotiator`, not
12
12
  * `@ggui-ai/mcp-server-core`.** `mcp-server-core` contains the
13
13
  * storage + runtime seams an MCP server implementer binds against
14
14
  * (`VectorStore`, `EmbeddingProvider`, `KeyValueStore`,
15
- * `BlueprintProvider`, `Negotiator`). `LLMCaller` is an
16
- * engine-internal dispatcher — one level below `Negotiator` — so
17
- * lifting it to `mcp-server-core` would grow the public seam count
18
- * speculatively. If a second consumer outside the negotiator
19
- * surfaces later, the "where does `LLMCaller` live?" question can be
20
- * re-opened at that point.
15
+ * `BlueprintProvider`). `LLMCaller` is an engine-internal
16
+ * dispatcher — one level below the synthesis + judge primitives this
17
+ * package exports — so lifting it to `mcp-server-core` would grow the
18
+ * public seam count speculatively. If a second consumer outside the
19
+ * negotiator surfaces later, the "where does `LLMCaller` live?"
20
+ * question can be re-opened at that point.
21
21
  *
22
22
  * Normative semantics:
23
23
  * - `call(systemPrompt, userMessage, maxTokens?)` returns the raw
@@ -43,7 +43,7 @@ export interface ToolSchema {
43
43
  input_schema: Record<string, unknown>;
44
44
  }
45
45
 
46
- /** Chat-model dispatcher consumed by the negotiator decision engine. */
46
+ /** Chat-model dispatcher consumed by the negotiator's synthesis + judge primitives. */
47
47
  export interface LLMCaller {
48
48
  /**
49
49
  * Call the model in plain-text mode. `maxTokens` defaults to