@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.
Files changed (91) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +49 -0
  3. package/dist/contract-hash.d.ts +54 -0
  4. package/dist/contract-hash.d.ts.map +1 -0
  5. package/dist/contract-hash.js +96 -0
  6. package/dist/contract-validators.d.ts +171 -0
  7. package/dist/contract-validators.d.ts.map +1 -0
  8. package/dist/contract-validators.js +478 -0
  9. package/dist/decision-input.d.ts +48 -0
  10. package/dist/decision-input.d.ts.map +1 -0
  11. package/dist/decision-input.js +14 -0
  12. package/dist/decision.d.ts +54 -0
  13. package/dist/decision.d.ts.map +1 -0
  14. package/dist/decision.js +500 -0
  15. package/dist/index.d.ts +36 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +25 -0
  18. package/dist/intent.d.ts +22 -0
  19. package/dist/intent.d.ts.map +1 -0
  20. package/dist/intent.js +28 -0
  21. package/dist/llm-caller.d.ts +70 -0
  22. package/dist/llm-caller.d.ts.map +1 -0
  23. package/dist/llm-caller.js +38 -0
  24. package/dist/llm-rerank.d.ts +101 -0
  25. package/dist/llm-rerank.d.ts.map +1 -0
  26. package/dist/llm-rerank.js +178 -0
  27. package/dist/negotiate.d.ts +141 -0
  28. package/dist/negotiate.d.ts.map +1 -0
  29. package/dist/negotiate.js +161 -0
  30. package/dist/normalize-schema.d.ts +22 -0
  31. package/dist/normalize-schema.d.ts.map +1 -0
  32. package/dist/normalize-schema.js +191 -0
  33. package/dist/pure.d.ts +30 -0
  34. package/dist/pure.d.ts.map +1 -0
  35. package/dist/pure.js +43 -0
  36. package/dist/rag-search.d.ts +73 -0
  37. package/dist/rag-search.d.ts.map +1 -0
  38. package/dist/rag-search.js +192 -0
  39. package/dist/rerank-eval/pairs.d.ts +28 -0
  40. package/dist/rerank-eval/pairs.d.ts.map +1 -0
  41. package/dist/rerank-eval/pairs.js +531 -0
  42. package/dist/rerank-eval/run-probe-cli.d.ts +3 -0
  43. package/dist/rerank-eval/run-probe-cli.d.ts.map +1 -0
  44. package/dist/rerank-eval/run-probe-cli.js +146 -0
  45. package/dist/rerank-eval/run-probe.d.ts +68 -0
  46. package/dist/rerank-eval/run-probe.d.ts.map +1 -0
  47. package/dist/rerank-eval/run-probe.js +113 -0
  48. package/dist/session.d.ts +42 -0
  49. package/dist/session.d.ts.map +1 -0
  50. package/dist/session.js +21 -0
  51. package/dist/suggestion.d.ts +38 -0
  52. package/dist/suggestion.d.ts.map +1 -0
  53. package/dist/suggestion.js +47 -0
  54. package/dist/synth-bench/corpus.d.ts +106 -0
  55. package/dist/synth-bench/corpus.d.ts.map +1 -0
  56. package/dist/synth-bench/corpus.js +994 -0
  57. package/dist/synth-bench/run-bench-cli.d.ts +3 -0
  58. package/dist/synth-bench/run-bench-cli.d.ts.map +1 -0
  59. package/dist/synth-bench/run-bench-cli.js +181 -0
  60. package/dist/synth-bench/run-bench.d.ts +101 -0
  61. package/dist/synth-bench/run-bench.d.ts.map +1 -0
  62. package/dist/synth-bench/run-bench.js +374 -0
  63. package/dist/synthesize-contract.d.ts +131 -0
  64. package/dist/synthesize-contract.d.ts.map +1 -0
  65. package/dist/synthesize-contract.js +948 -0
  66. package/dist/types.d.ts +30 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +13 -0
  69. package/package.json +74 -0
  70. package/src/contract-hash.ts +102 -0
  71. package/src/contract-validators.ts +604 -0
  72. package/src/decision-input.ts +49 -0
  73. package/src/decision.ts +581 -0
  74. package/src/index.ts +63 -0
  75. package/src/intent.ts +37 -0
  76. package/src/llm-caller.ts +82 -0
  77. package/src/llm-rerank.ts +280 -0
  78. package/src/negotiate.ts +312 -0
  79. package/src/normalize-schema.ts +193 -0
  80. package/src/pure.ts +46 -0
  81. package/src/rag-search.ts +274 -0
  82. package/src/rerank-eval/pairs.ts +624 -0
  83. package/src/rerank-eval/run-probe-cli.ts +197 -0
  84. package/src/rerank-eval/run-probe.ts +198 -0
  85. package/src/session.ts +41 -0
  86. package/src/suggestion.ts +73 -0
  87. package/src/synth-bench/corpus.ts +1126 -0
  88. package/src/synth-bench/run-bench-cli.ts +237 -0
  89. package/src/synth-bench/run-bench.ts +525 -0
  90. package/src/synthesize-contract.ts +1161 -0
  91. package/src/types.ts +31 -0
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Synthesizer bench CLI — live LLM probe.
4
+ *
5
+ * Reads ~/.ggui/credentials.json for the Anthropic API key, runs
6
+ * synthesizeContract over BENCH_CORPUS, prints per-shape precision
7
+ * + the redundant-action firing count + p50/p95 latency.
8
+ *
9
+ * Costs ~$0.001 per entry on Haiku 4.5 → roughly $0.05 for the full
10
+ * 50+ corpus run. NOT run in CI; opt-in only.
11
+ *
12
+ * Usage:
13
+ * pnpm -F @ggui-ai/negotiator bench-synth
14
+ * pnpm -F @ggui-ai/negotiator bench-synth -- --limit 10
15
+ * pnpm -F @ggui-ai/negotiator bench-synth -- --shape context+action
16
+ * ANTHROPIC_API_KEY=sk-... pnpm -F @ggui-ai/negotiator bench-synth
17
+ * pnpm -F @ggui-ai/negotiator bench-synth -- --json > report.json
18
+ *
19
+ * Bench-only — not exported from the package index.
20
+ */
21
+ import { readFileSync } from 'node:fs';
22
+ import { homedir } from 'node:os';
23
+ import { resolve as pathResolve } from 'node:path';
24
+ import { evaluateAgainstCorpus, formatBenchReport } from './run-bench.js';
25
+ import type { LLMCaller, ToolSchema } from '../llm-caller.js';
26
+ import { contractShape } from './corpus.js';
27
+
28
+ const ANTHROPIC_API = 'https://api.anthropic.com/v1/messages';
29
+ const DEFAULT_MODEL = 'claude-haiku-4-5';
30
+
31
+ interface CredsFile {
32
+ apps?: { global?: { anthropic?: string } };
33
+ }
34
+
35
+ function resolveAnthropicKey(): string {
36
+ const envKey = process.env['ANTHROPIC_API_KEY'];
37
+ if (envKey && envKey.length > 0) return envKey;
38
+ const credsPath = pathResolve(homedir(), '.ggui', 'credentials.json');
39
+ let parsed: CredsFile;
40
+ try {
41
+ parsed = JSON.parse(readFileSync(credsPath, 'utf8')) as CredsFile;
42
+ } catch (err) {
43
+ throw new Error(
44
+ `bench-synth: could not read ${credsPath} (${err instanceof Error ? err.message : String(err)}). Set ANTHROPIC_API_KEY env var or run \`ggui auth set anthropic\`.`,
45
+ );
46
+ }
47
+ const key = parsed.apps?.global?.anthropic;
48
+ if (typeof key !== 'string' || key.length === 0) {
49
+ throw new Error(
50
+ `bench-synth: no anthropic key found at apps.global.anthropic in ${credsPath}.`,
51
+ );
52
+ }
53
+ return key;
54
+ }
55
+
56
+ interface AnthropicContentBlock {
57
+ type: string;
58
+ name?: string;
59
+ input?: unknown;
60
+ text?: string;
61
+ }
62
+
63
+ interface AnthropicResponse {
64
+ content?: AnthropicContentBlock[];
65
+ usage?: { input_tokens?: number; output_tokens?: number };
66
+ stop_reason?: string;
67
+ error?: { type?: string; message?: string };
68
+ }
69
+
70
+ let totalInputTokens = 0;
71
+ let totalOutputTokens = 0;
72
+
73
+ function buildAnthropicLlmCaller(apiKey: string, model: string): LLMCaller {
74
+ return {
75
+ async call(): Promise<string> {
76
+ throw new Error('bench-synth: text-mode not exercised — synth uses callStructured');
77
+ },
78
+ async callStructured<T>(
79
+ systemPrompt: string,
80
+ userMessage: string,
81
+ tool: ToolSchema,
82
+ maxTokens?: number,
83
+ ): Promise<T> {
84
+ // `temperature` deprecated on Haiku 4.5+ — Anthropic rejects with
85
+ // HTTP 400. `tool_choice: { type: 'tool', name }` below already
86
+ // binds output to the input_schema; residual stochasticity stays
87
+ // bounded via canonical-key normalization downstream.
88
+ const body = {
89
+ model,
90
+ max_tokens: maxTokens ?? 1024,
91
+ system: systemPrompt,
92
+ messages: [{ role: 'user', content: userMessage }],
93
+ tools: [
94
+ {
95
+ name: tool.name,
96
+ description: tool.description,
97
+ input_schema: tool.input_schema,
98
+ },
99
+ ],
100
+ tool_choice: { type: 'tool', name: tool.name },
101
+ };
102
+ const res = await fetch(ANTHROPIC_API, {
103
+ method: 'POST',
104
+ headers: {
105
+ 'content-type': 'application/json',
106
+ 'x-api-key': apiKey,
107
+ 'anthropic-version': '2023-06-01',
108
+ },
109
+ body: JSON.stringify(body),
110
+ });
111
+ const json = (await res.json()) as AnthropicResponse;
112
+ if (!res.ok) {
113
+ const errType = json.error?.type ?? 'unknown';
114
+ const errMsg = json.error?.message ?? `HTTP ${res.status}`;
115
+ throw new Error(`anthropic ${errType}: ${errMsg}`);
116
+ }
117
+ if (json.usage) {
118
+ totalInputTokens += json.usage.input_tokens ?? 0;
119
+ totalOutputTokens += json.usage.output_tokens ?? 0;
120
+ }
121
+ const toolBlock = json.content?.find((b) => b.type === 'tool_use');
122
+ if (!toolBlock || toolBlock.input === undefined) {
123
+ throw new Error(
124
+ `anthropic: no tool_use block in response (stop_reason=${json.stop_reason ?? 'unknown'})`,
125
+ );
126
+ }
127
+ return toolBlock.input as T;
128
+ },
129
+ };
130
+ }
131
+
132
+ const SHAPE_FILTERS = [
133
+ 'props-only',
134
+ 'context-only',
135
+ 'context+action',
136
+ 'stream',
137
+ 'with-gadgets',
138
+ 'empty',
139
+ ] as const;
140
+
141
+ interface CliArgs {
142
+ limit?: number;
143
+ shapeFilter?: string;
144
+ model: string;
145
+ json: boolean;
146
+ }
147
+
148
+ function parseArgs(argv: readonly string[]): CliArgs {
149
+ let limit: number | undefined;
150
+ let shapeFilter: string | undefined;
151
+ let model = DEFAULT_MODEL;
152
+ let json = false;
153
+ for (let i = 0; i < argv.length; i++) {
154
+ const a = argv[i];
155
+ if (a === '--limit' && argv[i + 1]) {
156
+ limit = Number(argv[++i]);
157
+ } else if (a === '--shape' && argv[i + 1]) {
158
+ const v = argv[++i]!;
159
+ if ((SHAPE_FILTERS as readonly string[]).includes(v)) {
160
+ shapeFilter = v;
161
+ } else {
162
+ throw new Error(
163
+ `unknown shape: ${v} (expected one of ${SHAPE_FILTERS.join(', ')})`,
164
+ );
165
+ }
166
+ } else if (a === '--model' && argv[i + 1]) {
167
+ model = argv[++i]!;
168
+ } else if (a === '--json') {
169
+ json = true;
170
+ }
171
+ }
172
+ const result: CliArgs = { model, json };
173
+ if (limit !== undefined) result.limit = limit;
174
+ if (shapeFilter !== undefined) result.shapeFilter = shapeFilter;
175
+ return result;
176
+ }
177
+
178
+ const HAIKU_4_5_PRICE_INPUT_PER_TOKEN = 1.0 / 1_000_000;
179
+ const HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN = 5.0 / 1_000_000;
180
+
181
+ async function main(): Promise<void> {
182
+ const args = parseArgs(process.argv.slice(2));
183
+ const apiKey = resolveAnthropicKey();
184
+ const llm = buildAnthropicLlmCaller(apiKey, args.model);
185
+
186
+ if (!args.json) {
187
+ process.stdout.write(`bench-synth: model=${args.model}\n\n`);
188
+ }
189
+ const report = await evaluateAgainstCorpus(
190
+ { llm },
191
+ {
192
+ ...(args.limit !== undefined ? { limit: args.limit } : {}),
193
+ ...(args.shapeFilter !== undefined
194
+ ? { shapeFilter: args.shapeFilter }
195
+ : {}),
196
+ onProgress: args.json
197
+ ? undefined
198
+ : (outcome, idx, total) => {
199
+ const status = outcome.score.pass ? 'OK ' : 'NO ';
200
+ const id = outcome.entry.id.padEnd(28);
201
+ const shape = contractShape(outcome.entry.expected).padEnd(16);
202
+ process.stdout.write(
203
+ `[${status}] ${(idx + 1).toString().padStart(2)}/${total} ${shape} ${id} ${outcome.latencyMs}ms\n`,
204
+ );
205
+ },
206
+ },
207
+ );
208
+
209
+ if (args.json) {
210
+ process.stdout.write(JSON.stringify(report, null, 2));
211
+ process.stdout.write('\n');
212
+ return;
213
+ }
214
+
215
+ process.stdout.write('\n');
216
+ process.stdout.write(formatBenchReport(report));
217
+ process.stdout.write('\n\n');
218
+
219
+ const totalCost =
220
+ totalInputTokens * HAIKU_4_5_PRICE_INPUT_PER_TOKEN +
221
+ totalOutputTokens * HAIKU_4_5_PRICE_OUTPUT_PER_TOKEN;
222
+ const callsMade = report.totals.all - report.totals.synthDeclined;
223
+ const costPerCall = callsMade === 0 ? 0 : totalCost / callsMade;
224
+ process.stdout.write(
225
+ `Tokens: input=${totalInputTokens} output=${totalOutputTokens}\n`,
226
+ );
227
+ process.stdout.write(
228
+ `Cost: total=$${totalCost.toFixed(4)} per-call=$${costPerCall.toFixed(4)}\n`,
229
+ );
230
+ }
231
+
232
+ main().catch((err) => {
233
+ process.stderr.write(
234
+ `bench-synth failed: ${err instanceof Error ? err.message : String(err)}\n`,
235
+ );
236
+ process.exit(1);
237
+ });