@honua/mcp-server 0.0.10-alpha.0 → 0.1.0-beta.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 (176) hide show
  1. package/README.md +259 -15
  2. package/certification/geospatial-mcp-schemas/PROVENANCE.md +41 -0
  3. package/certification/geospatial-mcp-schemas/README.md +90 -0
  4. package/certification/geospatial-mcp-schemas/common/geoprocessing-error.schema.json +45 -0
  5. package/certification/geospatial-mcp-schemas/conformance/manifest.schema.json +79 -0
  6. package/certification/geospatial-mcp-schemas/index.json +390 -0
  7. package/certification/geospatial-mcp-schemas/resources/app-package.schema.json +20 -0
  8. package/certification/geospatial-mcp-schemas/resources/app-template.schema.json +15 -0
  9. package/certification/geospatial-mcp-schemas/resources/artifact.schema.json +69 -0
  10. package/certification/geospatial-mcp-schemas/resources/deployment.schema.json +27 -0
  11. package/certification/geospatial-mcp-schemas/resources/map-package.schema.json +21 -0
  12. package/certification/geospatial-mcp-schemas/resources/map-template.schema.json +15 -0
  13. package/certification/geospatial-mcp-schemas/resources/provenance.schema.json +50 -0
  14. package/certification/geospatial-mcp-schemas/resources/published-service.schema.json +21 -0
  15. package/certification/geospatial-mcp-schemas/resources/result-package.schema.json +64 -0
  16. package/certification/geospatial-mcp-schemas/resources/style.schema.json +12 -0
  17. package/certification/geospatial-mcp-schemas/resources/theme.schema.json +12 -0
  18. package/certification/geospatial-mcp-schemas/resources/workspace.schema.json +63 -0
  19. package/certification/geospatial-mcp-schemas/tools/apply_style_preset.schema.json +27 -0
  20. package/certification/geospatial-mcp-schemas/tools/buffer_features.schema.json +60 -0
  21. package/certification/geospatial-mcp-schemas/tools/cancel_job.schema.json +15 -0
  22. package/certification/geospatial-mcp-schemas/tools/clarify_intent.schema.json +69 -0
  23. package/certification/geospatial-mcp-schemas/tools/compose_mixed_protocol_map.schema.json +21 -0
  24. package/certification/geospatial-mcp-schemas/tools/create_app_package.schema.json +33 -0
  25. package/certification/geospatial-mcp-schemas/tools/create_map_package.schema.json +41 -0
  26. package/certification/geospatial-mcp-schemas/tools/edit_features.schema.json +74 -0
  27. package/certification/geospatial-mcp-schemas/tools/execute_plan.schema.json +64 -0
  28. package/certification/geospatial-mcp-schemas/tools/export_dataset.schema.json +60 -0
  29. package/certification/geospatial-mcp-schemas/tools/geocode_address.schema.json +36 -0
  30. package/certification/geospatial-mcp-schemas/tools/geocode_addresses.schema.json +34 -0
  31. package/certification/geospatial-mcp-schemas/tools/get_style.schema.json +40 -0
  32. package/certification/geospatial-mcp-schemas/tools/ground_candidates.schema.json +66 -0
  33. package/certification/geospatial-mcp-schemas/tools/ingest_dataset.schema.json +45 -0
  34. package/certification/geospatial-mcp-schemas/tools/join_features.schema.json +73 -0
  35. package/certification/geospatial-mcp-schemas/tools/list_capabilities.schema.json +21 -0
  36. package/certification/geospatial-mcp-schemas/tools/list_layers.schema.json +14 -0
  37. package/certification/geospatial-mcp-schemas/tools/overlay_features.schema.json +53 -0
  38. package/certification/geospatial-mcp-schemas/tools/plan_analysis.schema.json +19 -0
  39. package/certification/geospatial-mcp-schemas/tools/preview_app_package.schema.json +15 -0
  40. package/certification/geospatial-mcp-schemas/tools/preview_map_package.schema.json +17 -0
  41. package/certification/geospatial-mcp-schemas/tools/preview_package.schema.json +40 -0
  42. package/certification/geospatial-mcp-schemas/tools/propose_operation.schema.json +28 -0
  43. package/certification/geospatial-mcp-schemas/tools/publish_result.schema.json +29 -0
  44. package/certification/geospatial-mcp-schemas/tools/publish_service.schema.json +59 -0
  45. package/certification/geospatial-mcp-schemas/tools/query_features.schema.json +74 -0
  46. package/certification/geospatial-mcp-schemas/tools/refine_map_package.schema.json +22 -0
  47. package/certification/geospatial-mcp-schemas/tools/render_map.schema.json +60 -0
  48. package/certification/geospatial-mcp-schemas/tools/reproject_features.schema.json +49 -0
  49. package/certification/geospatial-mcp-schemas/tools/resolve_entity.schema.json +30 -0
  50. package/certification/geospatial-mcp-schemas/tools/solve_route.schema.json +39 -0
  51. package/certification/geospatial-mcp-schemas/tools/summarize_statistics.schema.json +71 -0
  52. package/certification/geospatial-mcp-schemas/tools/validate_package.schema.json +35 -0
  53. package/certification/geospatial-mcp-schemas/tools/validate_plan.schema.json +61 -0
  54. package/dist/src/capability.d.ts +48 -0
  55. package/dist/src/capability.d.ts.map +1 -0
  56. package/dist/src/capability.js +44 -0
  57. package/dist/src/certification/census-data.d.ts +33 -0
  58. package/dist/src/certification/census-data.d.ts.map +1 -0
  59. package/dist/src/certification/census-data.js +506 -0
  60. package/dist/src/certification/census-fixture-client.d.ts +7 -0
  61. package/dist/src/certification/census-fixture-client.d.ts.map +1 -0
  62. package/dist/src/certification/census-fixture-client.js +226 -0
  63. package/dist/src/certification/certifier.d.ts +125 -0
  64. package/dist/src/certification/certifier.d.ts.map +1 -0
  65. package/dist/src/certification/certifier.js +455 -0
  66. package/dist/src/certification/cli.d.ts +3 -0
  67. package/dist/src/certification/cli.d.ts.map +1 -0
  68. package/dist/src/certification/cli.js +63 -0
  69. package/dist/src/certification/contracts.d.ts +103 -0
  70. package/dist/src/certification/contracts.d.ts.map +1 -0
  71. package/dist/src/certification/contracts.js +245 -0
  72. package/dist/src/certification/fixture-client.d.ts +4 -0
  73. package/dist/src/certification/fixture-client.d.ts.map +1 -0
  74. package/dist/src/certification/fixture-client.js +113 -0
  75. package/dist/src/certification/fixtures.d.ts +45 -0
  76. package/dist/src/certification/fixtures.d.ts.map +1 -0
  77. package/dist/src/certification/fixtures.js +92 -0
  78. package/dist/src/certification/json-schema.d.ts +71 -0
  79. package/dist/src/certification/json-schema.d.ts.map +1 -0
  80. package/dist/src/certification/json-schema.js +172 -0
  81. package/dist/src/certification/lifecycle.d.ts +14 -0
  82. package/dist/src/certification/lifecycle.d.ts.map +1 -0
  83. package/dist/src/certification/lifecycle.js +326 -0
  84. package/dist/src/certification/mock-upstream.d.ts +29 -0
  85. package/dist/src/certification/mock-upstream.d.ts.map +1 -0
  86. package/dist/src/certification/mock-upstream.js +96 -0
  87. package/dist/src/certification/operator-catalog.d.ts +90 -0
  88. package/dist/src/certification/operator-catalog.d.ts.map +1 -0
  89. package/dist/src/certification/operator-catalog.js +650 -0
  90. package/dist/src/certification/report.d.ts +4 -0
  91. package/dist/src/certification/report.d.ts.map +1 -0
  92. package/dist/src/certification/report.js +103 -0
  93. package/dist/src/certification/run.d.ts +28 -0
  94. package/dist/src/certification/run.d.ts.map +1 -0
  95. package/dist/src/certification/run.js +32 -0
  96. package/dist/src/certification/schema-index.d.ts +94 -0
  97. package/dist/src/certification/schema-index.d.ts.map +1 -0
  98. package/dist/src/certification/schema-index.js +109 -0
  99. package/dist/src/certification/target.d.ts +48 -0
  100. package/dist/src/certification/target.d.ts.map +1 -0
  101. package/dist/src/certification/target.js +219 -0
  102. package/dist/src/eval/cli.d.ts +3 -0
  103. package/dist/src/eval/cli.d.ts.map +1 -0
  104. package/dist/src/eval/cli.js +118 -0
  105. package/dist/src/eval/corpus.d.ts +34 -0
  106. package/dist/src/eval/corpus.d.ts.map +1 -0
  107. package/dist/src/eval/corpus.js +150 -0
  108. package/dist/src/eval/drivers/anthropic.d.ts +14 -0
  109. package/dist/src/eval/drivers/anthropic.d.ts.map +1 -0
  110. package/dist/src/eval/drivers/anthropic.js +120 -0
  111. package/dist/src/eval/drivers/bedrock.d.ts +54 -0
  112. package/dist/src/eval/drivers/bedrock.d.ts.map +1 -0
  113. package/dist/src/eval/drivers/bedrock.js +178 -0
  114. package/dist/src/eval/drivers/deterministic.d.ts +17 -0
  115. package/dist/src/eval/drivers/deterministic.d.ts.map +1 -0
  116. package/dist/src/eval/drivers/deterministic.js +64 -0
  117. package/dist/src/eval/drivers/index.d.ts +34 -0
  118. package/dist/src/eval/drivers/index.d.ts.map +1 -0
  119. package/dist/src/eval/drivers/index.js +74 -0
  120. package/dist/src/eval/drivers/openai.d.ts +14 -0
  121. package/dist/src/eval/drivers/openai.d.ts.map +1 -0
  122. package/dist/src/eval/drivers/openai.js +136 -0
  123. package/dist/src/eval/grade.d.ts +14 -0
  124. package/dist/src/eval/grade.d.ts.map +1 -0
  125. package/dist/src/eval/grade.js +75 -0
  126. package/dist/src/eval/northstar-corpus.d.ts +3 -0
  127. package/dist/src/eval/northstar-corpus.d.ts.map +1 -0
  128. package/dist/src/eval/northstar-corpus.js +194 -0
  129. package/dist/src/eval/operator-corpus.d.ts +3 -0
  130. package/dist/src/eval/operator-corpus.d.ts.map +1 -0
  131. package/dist/src/eval/operator-corpus.js +193 -0
  132. package/dist/src/eval/report.d.ts +109 -0
  133. package/dist/src/eval/report.d.ts.map +1 -0
  134. package/dist/src/eval/report.js +133 -0
  135. package/dist/src/eval/runner.d.ts +28 -0
  136. package/dist/src/eval/runner.d.ts.map +1 -0
  137. package/dist/src/eval/runner.js +130 -0
  138. package/dist/src/eval/standalone-corpus.d.ts +3 -0
  139. package/dist/src/eval/standalone-corpus.d.ts.map +1 -0
  140. package/dist/src/eval/standalone-corpus.js +699 -0
  141. package/dist/src/eval/types.d.ts +118 -0
  142. package/dist/src/eval/types.d.ts.map +1 -0
  143. package/dist/src/eval/types.js +10 -0
  144. package/dist/src/helpers.d.ts +11 -0
  145. package/dist/src/helpers.d.ts.map +1 -1
  146. package/dist/src/helpers.js +22 -0
  147. package/dist/src/index.d.ts +31 -0
  148. package/dist/src/index.d.ts.map +1 -1
  149. package/dist/src/index.js +50 -4
  150. package/dist/src/provenance.d.ts +44 -0
  151. package/dist/src/provenance.d.ts.map +1 -0
  152. package/dist/src/provenance.js +37 -0
  153. package/dist/src/proxy.d.ts +46 -0
  154. package/dist/src/proxy.d.ts.map +1 -0
  155. package/dist/src/proxy.js +124 -0
  156. package/dist/src/resources/styles.d.ts +29 -0
  157. package/dist/src/resources/styles.d.ts.map +1 -0
  158. package/dist/src/resources/styles.js +76 -0
  159. package/dist/src/styles.d.ts +112 -0
  160. package/dist/src/styles.d.ts.map +1 -0
  161. package/dist/src/styles.js +144 -0
  162. package/dist/src/tools/apply-style-preset.d.ts +27 -0
  163. package/dist/src/tools/apply-style-preset.d.ts.map +1 -0
  164. package/dist/src/tools/apply-style-preset.js +41 -0
  165. package/dist/src/tools/count-features.d.ts.map +1 -1
  166. package/dist/src/tools/count-features.js +5 -4
  167. package/dist/src/tools/get-extent.d.ts.map +1 -1
  168. package/dist/src/tools/get-extent.js +6 -4
  169. package/dist/src/tools/get-style.d.ts +24 -0
  170. package/dist/src/tools/get-style.d.ts.map +1 -0
  171. package/dist/src/tools/get-style.js +39 -0
  172. package/dist/src/tools/list-services.d.ts.map +1 -1
  173. package/dist/src/tools/list-services.js +16 -1
  174. package/dist/src/tools/query-features.d.ts.map +1 -1
  175. package/dist/src/tools/query-features.js +4 -1
  176. package/package.json +28 -4
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Claude-via-Amazon-Bedrock driver for the cross-model eval (honua-server #1956).
3
+ *
4
+ * This is the "any client LLM" counterpart to the direct-Anthropic driver, but it
5
+ * reaches Claude through Amazon Bedrock's Converse API instead of the Anthropic
6
+ * API. It exists so the AI proof can run on infrastructure that has AWS Bedrock
7
+ * access (default `~/.aws` credential chain) but no ANTHROPIC_API_KEY — exactly
8
+ * the situation on the build host. The SAME corpus and grading are driven over
9
+ * the identical MCP catalog, so the artifact compares success / clarification /
10
+ * edit rates across vendors and transports.
11
+ *
12
+ * Auth uses the standard AWS SDK credential provider chain (env vars, shared
13
+ * config/credentials files, SSO, instance/role credentials) — no key is ever
14
+ * hardcoded. Because that chain is resolved asynchronously and cannot be sniffed
15
+ * synchronously, the driver opts in via the `HONUA_EVAL_BEDROCK` env flag (set by
16
+ * `resolveDrivers`) or by explicit selection (`--driver bedrock`).
17
+ *
18
+ * Model id: defaults to `anthropic.claude-sonnet-4-5-20250929-v1:0`, overridable
19
+ * via `HONUA_EVAL_BEDROCK_MODEL`. NOTE: most current Anthropic models on Bedrock
20
+ * are not enabled for on-demand throughput by the bare id and must be invoked
21
+ * through a cross-region inference profile — i.e. the `us.`-prefixed form
22
+ * `us.anthropic.claude-sonnet-4-5-20250929-v1:0` in us.* regions. Set
23
+ * `HONUA_EVAL_BEDROCK_MODEL` accordingly for live runs.
24
+ *
25
+ * Region: `AWS_REGION` if set, else `us-west-2`.
26
+ *
27
+ * `@aws-sdk/client-bedrock-runtime` is a declared dependency but is imported
28
+ * dynamically (variable specifier) so the offline CI path — which only runs the
29
+ * deterministic control — never needs to load it, and a graceful `driverError`
30
+ * is recorded if it is somehow absent.
31
+ *
32
+ * Live cross-model runs are tracked under honua-io/honua-server#1956.
33
+ */
34
+ const ASK_USER_TOOL_NAME = "ask_user";
35
+ const ASK_USER_TOOL = {
36
+ toolSpec: {
37
+ name: ASK_USER_TOOL_NAME,
38
+ description: "Call this ONLY if the request is too ambiguous to proceed. Provide the clarifying question you would ask.",
39
+ inputSchema: {
40
+ json: {
41
+ type: "object",
42
+ properties: { question: { type: "string" } },
43
+ required: ["question"],
44
+ additionalProperties: false,
45
+ },
46
+ },
47
+ },
48
+ };
49
+ const SYSTEM_PROMPT = "You are a GIS analyst working through the Honua MCP tools. Complete the user's task by calling the available tools. " +
50
+ "Ground every step in real tool results — do not invent service names, layers, or fields. " +
51
+ "If, and only if, the request is genuinely ambiguous and you cannot proceed, call ask_user. " +
52
+ "When finished, give a concise final answer that references what the tools returned.";
53
+ const MAX_ITERATIONS = 8;
54
+ const MAX_TOKENS = 4096;
55
+ const DEFAULT_MODEL = "anthropic.claude-sonnet-4-5-20250929-v1:0";
56
+ const DEFAULT_REGION = "us-west-2";
57
+ export class BedrockDriver {
58
+ vendor = "bedrock";
59
+ id;
60
+ region;
61
+ enabled;
62
+ constructor(options = {}) {
63
+ this.id = options.model ?? DEFAULT_MODEL;
64
+ this.region = options.region ?? DEFAULT_REGION;
65
+ const flag = options.enabled ?? process.env.HONUA_EVAL_BEDROCK;
66
+ this.enabled = isTruthy(flag);
67
+ }
68
+ isAvailable() {
69
+ return this.enabled;
70
+ }
71
+ async runWorkflow(scenario, ctx) {
72
+ const steps = [];
73
+ const transcript = {
74
+ scenarioId: scenario.id,
75
+ modelId: this.id,
76
+ steps,
77
+ finalAnswer: "",
78
+ clarificationRequested: false,
79
+ errorCount: 0,
80
+ };
81
+ let runtime;
82
+ try {
83
+ // Variable specifier → not statically resolved; keeps the AWS SDK an
84
+ // optional load for live runs only and the build independent of it.
85
+ const specifier = "@aws-sdk/client-bedrock-runtime";
86
+ runtime = (await import(specifier));
87
+ }
88
+ catch {
89
+ transcript.driverError = "@aws-sdk/client-bedrock-runtime is not installed (required for live Bedrock eval runs)";
90
+ return transcript;
91
+ }
92
+ const { BedrockRuntimeClient, ConverseCommand } = runtime;
93
+ const client = new BedrockRuntimeClient({ region: this.region });
94
+ const toolConfig = { tools: [...toBedrockTools(ctx.tools), ASK_USER_TOOL] };
95
+ const messages = [{ role: "user", content: [{ text: scenario.prompt }] }];
96
+ try {
97
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
98
+ const response = await client.send(new ConverseCommand({
99
+ modelId: this.id,
100
+ system: [{ text: SYSTEM_PROMPT }],
101
+ messages,
102
+ toolConfig,
103
+ inferenceConfig: { maxTokens: MAX_TOKENS },
104
+ }));
105
+ const content = response.output?.message?.content ?? [];
106
+ const toolUses = extractToolUses(content);
107
+ const text = extractText(content);
108
+ if (response.stopReason !== "tool_use" || toolUses.length === 0) {
109
+ transcript.finalAnswer = text;
110
+ return transcript;
111
+ }
112
+ messages.push({ role: "assistant", content });
113
+ const toolResults = [];
114
+ for (const use of toolUses) {
115
+ if (use.name === ASK_USER_TOOL_NAME) {
116
+ transcript.clarificationRequested = true;
117
+ transcript.finalAnswer = text;
118
+ return transcript;
119
+ }
120
+ const args = (use.input ?? {});
121
+ const result = await ctx.callTool(use.name, args);
122
+ steps.push({ tool: use.name, args, isError: result.isError });
123
+ if (result.isError) {
124
+ transcript.errorCount++;
125
+ }
126
+ toolResults.push(toToolResultBlock(use.toolUseId, result.text, result.isError));
127
+ }
128
+ messages.push({ role: "user", content: toolResults });
129
+ }
130
+ transcript.driverError = `exceeded ${MAX_ITERATIONS} tool-use iterations without finishing`;
131
+ return transcript;
132
+ }
133
+ catch (err) {
134
+ transcript.driverError = err instanceof Error ? err.message : String(err);
135
+ return transcript;
136
+ }
137
+ }
138
+ }
139
+ function isTruthy(value) {
140
+ if (typeof value !== "string") {
141
+ return false;
142
+ }
143
+ const v = value.trim().toLowerCase();
144
+ return v.length > 0 && v !== "0" && v !== "false" && v !== "no" && v !== "off";
145
+ }
146
+ /** Map MCP tool descriptors to Bedrock Converse `toolSpec` entries. */
147
+ export function toBedrockTools(tools) {
148
+ return tools.map((t) => ({
149
+ toolSpec: {
150
+ name: t.name,
151
+ description: t.description,
152
+ inputSchema: { json: (t.inputSchema ?? { type: "object" }) },
153
+ },
154
+ }));
155
+ }
156
+ /** Pull tool-use blocks out of a Converse content array. */
157
+ export function extractToolUses(content) {
158
+ return content
159
+ .map((b) => b.toolUse)
160
+ .filter((u) => typeof u === "object" && u !== null && typeof u.name === "string");
161
+ }
162
+ /** Concatenate text blocks of a Converse content array. */
163
+ export function extractText(content) {
164
+ return content
165
+ .map((b) => b.text)
166
+ .filter((t) => typeof t === "string")
167
+ .join("\n");
168
+ }
169
+ /** Build a Converse `toolResult` content block to feed back to the model. */
170
+ export function toToolResultBlock(toolUseId, text, isError) {
171
+ return {
172
+ toolResult: {
173
+ toolUseId,
174
+ content: [{ text }],
175
+ status: isError ? "error" : "success",
176
+ },
177
+ };
178
+ }
@@ -0,0 +1,17 @@
1
+ import type { ModelDriver, Scenario, WorkflowContext, WorkflowTranscript } from "../types.js";
2
+ /**
3
+ * Deterministic offline driver — the CI control for the cross-model eval.
4
+ *
5
+ * It does not call any model. Instead it executes the scenario's scripted "ideal
6
+ * client" tool trajectory against the REAL MCP surface (the same `tools/call`
7
+ * round-trips a live LLM would make), then synthesizes a final answer from the
8
+ * concatenated tool results. This makes the offline eval a genuine end-to-end
9
+ * exercise of the MCP surface — not a stub — while staying fully reproducible.
10
+ */
11
+ export declare class DeterministicDriver implements ModelDriver {
12
+ readonly id = "deterministic";
13
+ readonly vendor: "deterministic";
14
+ isAvailable(): boolean;
15
+ runWorkflow(scenario: Scenario, ctx: WorkflowContext): Promise<WorkflowTranscript>;
16
+ }
17
+ //# sourceMappingURL=deterministic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deterministic.d.ts","sourceRoot":"","sources":["../../../../src/eval/drivers/deterministic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAkB,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE9G;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,YAAW,WAAW;IACrD,QAAQ,CAAC,EAAE,mBAAmB;IAC9B,QAAQ,CAAC,MAAM,EAAG,eAAe,CAAU;IAE3C,WAAW,IAAI,OAAO;IAIhB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAoDzF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Deterministic offline driver — the CI control for the cross-model eval.
3
+ *
4
+ * It does not call any model. Instead it executes the scenario's scripted "ideal
5
+ * client" tool trajectory against the REAL MCP surface (the same `tools/call`
6
+ * round-trips a live LLM would make), then synthesizes a final answer from the
7
+ * concatenated tool results. This makes the offline eval a genuine end-to-end
8
+ * exercise of the MCP surface — not a stub — while staying fully reproducible.
9
+ */
10
+ export class DeterministicDriver {
11
+ id = "deterministic";
12
+ vendor = "deterministic";
13
+ isAvailable() {
14
+ return true;
15
+ }
16
+ async runWorkflow(scenario, ctx) {
17
+ const advertised = new Set(ctx.tools.map((t) => t.name));
18
+ const steps = [];
19
+ const answerParts = [];
20
+ let errorCount = 0;
21
+ // Ambiguity/refusal scenarios: the ideal client asks for clarification rather
22
+ // than guessing. Honoring this deterministically lets the offline control
23
+ // exercise refusal/clarification behavior, not just happy-path trajectories.
24
+ if (scenario.clarify) {
25
+ return {
26
+ scenarioId: scenario.id,
27
+ modelId: this.id,
28
+ steps,
29
+ finalAnswer: scenario.clarify.question,
30
+ clarificationRequested: true,
31
+ errorCount,
32
+ };
33
+ }
34
+ for (const planned of scenario.script) {
35
+ if (!advertised.has(planned.tool)) {
36
+ // The scripted tool is not on the surface — record a driver-visible error
37
+ // (a real catalog regression would surface here, not silently pass).
38
+ return {
39
+ scenarioId: scenario.id,
40
+ modelId: this.id,
41
+ steps,
42
+ finalAnswer: answerParts.join("\n"),
43
+ clarificationRequested: false,
44
+ errorCount,
45
+ driverError: `scripted tool not advertised by the MCP surface: ${planned.tool}`,
46
+ };
47
+ }
48
+ const result = await ctx.callTool(planned.tool, planned.args);
49
+ steps.push({ tool: planned.tool, args: planned.args, isError: result.isError });
50
+ if (result.isError) {
51
+ errorCount++;
52
+ }
53
+ answerParts.push(result.text);
54
+ }
55
+ return {
56
+ scenarioId: scenario.id,
57
+ modelId: this.id,
58
+ steps,
59
+ finalAnswer: answerParts.join("\n"),
60
+ clarificationRequested: false,
61
+ errorCount,
62
+ };
63
+ }
64
+ }
@@ -0,0 +1,34 @@
1
+ import type { ModelDriver } from "../types.js";
2
+ import { AnthropicDriver } from "./anthropic.js";
3
+ import { BedrockDriver } from "./bedrock.js";
4
+ import { DeterministicDriver } from "./deterministic.js";
5
+ import { OpenAiDriver } from "./openai.js";
6
+ export { AnthropicDriver, BedrockDriver, DeterministicDriver, OpenAiDriver };
7
+ /** Names accepted by `selectDrivers` / the CLI `--driver` flag. */
8
+ export type DriverName = "deterministic" | "anthropic" | "openai" | "bedrock";
9
+ export interface ResolveDriversOptions {
10
+ env?: NodeJS.ProcessEnv;
11
+ /** Include live drivers even if their key/opt-in is absent (they record driverError). */
12
+ includeUnavailable?: boolean;
13
+ }
14
+ /**
15
+ * Resolve the set of model drivers to run.
16
+ *
17
+ * The deterministic control is always included. Claude (Anthropic API) and GPT
18
+ * drivers are added when their API keys are present; the Claude-via-Bedrock driver
19
+ * is added when `HONUA_EVAL_BEDROCK` is set (its auth comes from the AWS credential
20
+ * chain, which can't be sniffed from a single env var). This gates live runs behind
21
+ * env without hardcoding any credentials. CI runs the deterministic driver only —
22
+ * a green, reproducible artifact with no model/API calls.
23
+ */
24
+ export declare function resolveDrivers(options?: ResolveDriversOptions): ModelDriver[];
25
+ /**
26
+ * Build the driver set for an explicit `--driver` selection. The deterministic
27
+ * control is always included (it is the CI gate). Named live drivers are always
28
+ * constructed and run — even if their key/opt-in is absent — so an explicit
29
+ * request surfaces an honest `driverError` rather than being silently dropped.
30
+ */
31
+ export declare function selectDrivers(names: DriverName[], options?: ResolveDriversOptions): ModelDriver[];
32
+ /** Parse/validate a `--driver` value into a known driver name. */
33
+ export declare function parseDriverName(value: string): DriverName;
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/eval/drivers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;AAE7E,mEAAmE;AACnE,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,yFAAyF;IACzF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,WAAW,EAAE,CAwBjF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,WAAW,EAAE,CAuBrG;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAMzD"}
@@ -0,0 +1,74 @@
1
+ import { AnthropicDriver } from "./anthropic.js";
2
+ import { BedrockDriver } from "./bedrock.js";
3
+ import { DeterministicDriver } from "./deterministic.js";
4
+ import { OpenAiDriver } from "./openai.js";
5
+ export { AnthropicDriver, BedrockDriver, DeterministicDriver, OpenAiDriver };
6
+ /**
7
+ * Resolve the set of model drivers to run.
8
+ *
9
+ * The deterministic control is always included. Claude (Anthropic API) and GPT
10
+ * drivers are added when their API keys are present; the Claude-via-Bedrock driver
11
+ * is added when `HONUA_EVAL_BEDROCK` is set (its auth comes from the AWS credential
12
+ * chain, which can't be sniffed from a single env var). This gates live runs behind
13
+ * env without hardcoding any credentials. CI runs the deterministic driver only —
14
+ * a green, reproducible artifact with no model/API calls.
15
+ */
16
+ export function resolveDrivers(options = {}) {
17
+ const env = options.env ?? process.env;
18
+ const drivers = [new DeterministicDriver()];
19
+ const anthropic = new AnthropicDriver({ apiKey: env.ANTHROPIC_API_KEY, model: env.HONUA_EVAL_ANTHROPIC_MODEL });
20
+ if (options.includeUnavailable || anthropic.isAvailable()) {
21
+ drivers.push(anthropic);
22
+ }
23
+ const openai = new OpenAiDriver({ apiKey: env.OPENAI_API_KEY, model: env.OPENAI_MODEL });
24
+ if (options.includeUnavailable || openai.isAvailable()) {
25
+ drivers.push(openai);
26
+ }
27
+ const bedrock = new BedrockDriver({
28
+ enabled: env.HONUA_EVAL_BEDROCK,
29
+ model: env.HONUA_EVAL_BEDROCK_MODEL,
30
+ region: env.AWS_REGION,
31
+ });
32
+ if (options.includeUnavailable || bedrock.isAvailable()) {
33
+ drivers.push(bedrock);
34
+ }
35
+ return drivers;
36
+ }
37
+ /**
38
+ * Build the driver set for an explicit `--driver` selection. The deterministic
39
+ * control is always included (it is the CI gate). Named live drivers are always
40
+ * constructed and run — even if their key/opt-in is absent — so an explicit
41
+ * request surfaces an honest `driverError` rather than being silently dropped.
42
+ */
43
+ export function selectDrivers(names, options = {}) {
44
+ const env = options.env ?? process.env;
45
+ const drivers = [new DeterministicDriver()];
46
+ for (const name of names) {
47
+ switch (name) {
48
+ case "deterministic":
49
+ break; // already included
50
+ case "anthropic":
51
+ drivers.push(new AnthropicDriver({ apiKey: env.ANTHROPIC_API_KEY, model: env.HONUA_EVAL_ANTHROPIC_MODEL }));
52
+ break;
53
+ case "openai":
54
+ drivers.push(new OpenAiDriver({ apiKey: env.OPENAI_API_KEY, model: env.OPENAI_MODEL }));
55
+ break;
56
+ case "bedrock":
57
+ drivers.push(new BedrockDriver({ enabled: "1", model: env.HONUA_EVAL_BEDROCK_MODEL, region: env.AWS_REGION }));
58
+ break;
59
+ default: {
60
+ const exhaustive = name;
61
+ throw new Error(`unknown driver: ${String(exhaustive)}`);
62
+ }
63
+ }
64
+ }
65
+ return drivers;
66
+ }
67
+ /** Parse/validate a `--driver` value into a known driver name. */
68
+ export function parseDriverName(value) {
69
+ const known = ["deterministic", "anthropic", "openai", "bedrock"];
70
+ if (known.includes(value)) {
71
+ return value;
72
+ }
73
+ throw new Error(`unknown --driver "${value}" (expected one of: ${known.join(", ")})`);
74
+ }
@@ -0,0 +1,14 @@
1
+ import type { ModelDriver, Scenario, WorkflowContext, WorkflowTranscript } from "../types.js";
2
+ export interface OpenAiDriverOptions {
3
+ apiKey?: string | undefined;
4
+ model?: string;
5
+ }
6
+ export declare class OpenAiDriver implements ModelDriver {
7
+ readonly vendor: "openai";
8
+ readonly id: string;
9
+ private readonly apiKey;
10
+ constructor(options?: OpenAiDriverOptions);
11
+ isAvailable(): boolean;
12
+ runWorkflow(scenario: Scenario, ctx: WorkflowContext): Promise<WorkflowTranscript>;
13
+ }
14
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../../src/eval/drivers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAkB,eAAe,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAkD9G,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,YAAa,YAAW,WAAW;IAC9C,QAAQ,CAAC,MAAM,EAAG,QAAQ,CAAU;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,OAAO,GAAE,mBAAwB;IAK7C,WAAW,IAAI,OAAO;IAIhB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC;CA4EzF"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * GPT client driver for the cross-model eval (honua-server #1956).
3
+ *
4
+ * Gated behind OPENAI_API_KEY and a dynamically-imported `openai` SDK — neither
5
+ * is a dependency of this package, so the offline CI path never needs them. This
6
+ * is the "any client" counterpart to the Claude driver: the SAME corpus and the
7
+ * SAME grading are driven by a GPT model over the identical MCP catalog, so the
8
+ * artifact can compare success / clarification / edit rates across vendors.
9
+ *
10
+ * Live cross-model runs are tracked under honua-io/honua-server#1956.
11
+ */
12
+ const ASK_USER_FUNCTION = {
13
+ type: "function",
14
+ function: {
15
+ name: "ask_user",
16
+ description: "Call this ONLY if the request is too ambiguous to proceed. Provide the clarifying question you would ask.",
17
+ parameters: {
18
+ type: "object",
19
+ properties: { question: { type: "string" } },
20
+ required: ["question"],
21
+ additionalProperties: false,
22
+ },
23
+ },
24
+ };
25
+ const SYSTEM_PROMPT = "You are a GIS analyst working through the Honua MCP tools. Complete the user's task by calling the available tools. " +
26
+ "Ground every step in real tool results — do not invent service names, layers, or fields. " +
27
+ "If, and only if, the request is genuinely ambiguous and you cannot proceed, call ask_user. " +
28
+ "When finished, give a concise final answer that references what the tools returned.";
29
+ const MAX_ITERATIONS = 8;
30
+ /**
31
+ * Default GPT model for live cross-model runs.
32
+ *
33
+ * `gpt-5.5` is OpenAI's generally-available flagship reasoning model as of
34
+ * July 2026 (the apex of the public Chat Completions catalog; see
35
+ * developers.openai.com/api/docs/models/all). The newer GPT-5.6 "Sol" family was
36
+ * still preview-gated to a handful of orgs at that date and not callable by an
37
+ * ordinary API key, so it is NOT the default — set `OPENAI_MODEL=gpt-5.6-sol`
38
+ * (or any other id) to override once you have access. The resolved id is recorded
39
+ * per-driver in the results artifact (`models[].model`) so published comparisons
40
+ * are attributable to an exact model.
41
+ */
42
+ const DEFAULT_MODEL = "gpt-5.5";
43
+ export class OpenAiDriver {
44
+ vendor = "openai";
45
+ id;
46
+ apiKey;
47
+ constructor(options = {}) {
48
+ this.id = options.model ?? process.env.OPENAI_MODEL ?? DEFAULT_MODEL;
49
+ this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
50
+ }
51
+ isAvailable() {
52
+ return typeof this.apiKey === "string" && this.apiKey.length > 0;
53
+ }
54
+ async runWorkflow(scenario, ctx) {
55
+ const steps = [];
56
+ const transcript = {
57
+ scenarioId: scenario.id,
58
+ modelId: this.id,
59
+ steps,
60
+ finalAnswer: "",
61
+ clarificationRequested: false,
62
+ errorCount: 0,
63
+ };
64
+ let OpenAI;
65
+ try {
66
+ const specifier = "openai";
67
+ const mod = (await import(specifier));
68
+ OpenAI = mod.default;
69
+ }
70
+ catch {
71
+ transcript.driverError = "openai SDK is not installed (required for live GPT eval runs)";
72
+ return transcript;
73
+ }
74
+ if (!this.apiKey) {
75
+ transcript.driverError = "OPENAI_API_KEY is not set";
76
+ return transcript;
77
+ }
78
+ const client = new OpenAI({ apiKey: this.apiKey });
79
+ const tools = [
80
+ ...ctx.tools.map((t) => ({
81
+ type: "function",
82
+ function: { name: t.name, description: t.description, parameters: t.inputSchema },
83
+ })),
84
+ ASK_USER_FUNCTION,
85
+ ];
86
+ const messages = [
87
+ { role: "system", content: SYSTEM_PROMPT },
88
+ { role: "user", content: scenario.prompt },
89
+ ];
90
+ try {
91
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
92
+ const response = await client.chat.completions.create({ model: this.id, messages, tools });
93
+ const message = response.choices[0]?.message;
94
+ if (!message) {
95
+ transcript.driverError = "no choice returned by the model";
96
+ return transcript;
97
+ }
98
+ const toolCalls = message.tool_calls ?? [];
99
+ if (toolCalls.length === 0) {
100
+ transcript.finalAnswer = message.content ?? "";
101
+ return transcript;
102
+ }
103
+ messages.push({ role: "assistant", content: message.content ?? null, tool_calls: toolCalls });
104
+ for (const call of toolCalls) {
105
+ const args = safeParse(call.function.arguments);
106
+ if (call.function.name === "ask_user") {
107
+ transcript.clarificationRequested = true;
108
+ transcript.finalAnswer = message.content ?? "";
109
+ return transcript;
110
+ }
111
+ const result = await ctx.callTool(call.function.name, args);
112
+ steps.push({ tool: call.function.name, args, isError: result.isError });
113
+ if (result.isError) {
114
+ transcript.errorCount++;
115
+ }
116
+ messages.push({ role: "tool", tool_call_id: call.id, content: result.text });
117
+ }
118
+ }
119
+ transcript.driverError = `exceeded ${MAX_ITERATIONS} tool-use iterations without finishing`;
120
+ return transcript;
121
+ }
122
+ catch (err) {
123
+ transcript.driverError = err instanceof Error ? err.message : String(err);
124
+ return transcript;
125
+ }
126
+ }
127
+ }
128
+ function safeParse(raw) {
129
+ try {
130
+ const parsed = JSON.parse(raw);
131
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
132
+ }
133
+ catch {
134
+ return {};
135
+ }
136
+ }
@@ -0,0 +1,14 @@
1
+ import type { Scenario, ScenarioGrade, WorkflowTranscript } from "./types.js";
2
+ /** True if `sequence` appears as an ordered (not necessarily contiguous) subsequence of `steps`. */
3
+ export declare function isOrderedSubsequence(steps: string[], sequence: string[]): boolean;
4
+ /**
5
+ * Grade a transcript against a scenario's declarative success criteria.
6
+ *
7
+ * Outcomes:
8
+ * - `error`: the driver failed to run (unavailable model, exception).
9
+ * - `clarified`: the driver asked a clarifying question instead of completing.
10
+ * - `pass`: all criteria satisfied.
11
+ * - `fail`: completed but violated one or more criteria.
12
+ */
13
+ export declare function grade(scenario: Scenario, transcript: WorkflowTranscript): ScenarioGrade;
14
+ //# sourceMappingURL=grade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grade.d.ts","sourceRoot":"","sources":["../../../src/eval/grade.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9E,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAQjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,GAAG,aAAa,CAgEvF"}
@@ -0,0 +1,75 @@
1
+ /** True if `sequence` appears as an ordered (not necessarily contiguous) subsequence of `steps`. */
2
+ export function isOrderedSubsequence(steps, sequence) {
3
+ let i = 0;
4
+ for (const step of steps) {
5
+ if (i < sequence.length && step === sequence[i]) {
6
+ i++;
7
+ }
8
+ }
9
+ return i === sequence.length;
10
+ }
11
+ /**
12
+ * Grade a transcript against a scenario's declarative success criteria.
13
+ *
14
+ * Outcomes:
15
+ * - `error`: the driver failed to run (unavailable model, exception).
16
+ * - `clarified`: the driver asked a clarifying question instead of completing.
17
+ * - `pass`: all criteria satisfied.
18
+ * - `fail`: completed but violated one or more criteria.
19
+ */
20
+ export function grade(scenario, transcript) {
21
+ const base = {
22
+ scenarioId: scenario.id,
23
+ modelId: transcript.modelId,
24
+ errorCount: transcript.errorCount,
25
+ };
26
+ const { criteria } = scenario;
27
+ if (transcript.driverError) {
28
+ return { ...base, outcome: "error", violations: [transcript.driverError] };
29
+ }
30
+ if (transcript.clarificationRequested) {
31
+ // For an ambiguity/refusal scenario, clarifying IS the correct behavior.
32
+ if (criteria.expectClarification) {
33
+ return { ...base, outcome: "pass", violations: [] };
34
+ }
35
+ return { ...base, outcome: "clarified", violations: ["driver requested clarification"] };
36
+ }
37
+ if (criteria.expectClarification) {
38
+ // The prompt was ambiguous/unsupported but the driver answered anyway.
39
+ return { ...base, outcome: "fail", violations: ["expected a clarifying question but the driver completed"] };
40
+ }
41
+ const violations = [];
42
+ const calledTools = transcript.steps.map((s) => s.tool);
43
+ const calledSet = new Set(calledTools);
44
+ for (const required of criteria.requiredTools) {
45
+ if (!calledSet.has(required)) {
46
+ violations.push(`required tool not called: ${required}`);
47
+ }
48
+ }
49
+ if (criteria.expectedToolSequence && !isOrderedSubsequence(calledTools, criteria.expectedToolSequence)) {
50
+ violations.push(`expected tool sequence not observed: ${criteria.expectedToolSequence.join(" -> ")}`);
51
+ }
52
+ for (const forbidden of criteria.forbiddenTools ?? []) {
53
+ if (calledSet.has(forbidden)) {
54
+ violations.push(`forbidden tool called: ${forbidden}`);
55
+ }
56
+ }
57
+ const answer = transcript.finalAnswer.toLowerCase();
58
+ for (const fragment of criteria.answerMustInclude ?? []) {
59
+ if (!answer.includes(fragment.toLowerCase())) {
60
+ violations.push(`answer missing expected content: "${fragment}"`);
61
+ }
62
+ }
63
+ // Semantic assertions (issue #369): meaning, not just trajectory.
64
+ for (const fragment of criteria.answerMustNotInclude ?? []) {
65
+ if (answer.includes(fragment.toLowerCase())) {
66
+ violations.push(`answer contains forbidden content: "${fragment}"`);
67
+ }
68
+ }
69
+ for (const pattern of criteria.answerMustMatch ?? []) {
70
+ if (!new RegExp(pattern, "i").test(transcript.finalAnswer)) {
71
+ violations.push(`answer did not match required pattern: /${pattern}/i`);
72
+ }
73
+ }
74
+ return { ...base, outcome: violations.length === 0 ? "pass" : "fail", violations };
75
+ }
@@ -0,0 +1,3 @@
1
+ import type { Scenario } from "./types.js";
2
+ export declare const NORTHSTAR_CORPUS: Scenario[];
3
+ //# sourceMappingURL=northstar-corpus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"northstar-corpus.d.ts","sourceRoot":"","sources":["../../../src/eval/northstar-corpus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAiF3C,eAAO,MAAM,gBAAgB,EAAE,QAAQ,EAyHtC,CAAC"}