ai-capability-framework 1.0.3 → 1.1.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 (246) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +38 -30
  3. package/ROADMAP.md +14 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +32 -1
  6. package/dist/discovery/bindings.d.ts +7 -0
  7. package/dist/discovery/bindings.d.ts.map +1 -0
  8. package/dist/discovery/bindings.js +24 -0
  9. package/dist/discovery/budget.d.ts +17 -0
  10. package/dist/discovery/budget.d.ts.map +1 -0
  11. package/dist/discovery/budget.js +77 -0
  12. package/dist/discovery/configuration.d.ts +10 -0
  13. package/dist/discovery/configuration.d.ts.map +1 -0
  14. package/dist/discovery/configuration.js +63 -0
  15. package/dist/discovery/corpus.d.ts +8 -0
  16. package/dist/discovery/corpus.d.ts.map +1 -0
  17. package/dist/discovery/corpus.js +64 -0
  18. package/dist/discovery/diagnostics.d.ts +15 -0
  19. package/dist/discovery/diagnostics.d.ts.map +1 -0
  20. package/dist/discovery/diagnostics.js +11 -0
  21. package/dist/discovery/discoverer.d.ts +23 -0
  22. package/dist/discovery/discoverer.d.ts.map +1 -0
  23. package/dist/discovery/discoverer.js +261 -0
  24. package/dist/discovery/embedding-configuration.d.ts +12 -0
  25. package/dist/discovery/embedding-configuration.d.ts.map +1 -0
  26. package/dist/discovery/embedding-configuration.js +81 -0
  27. package/dist/discovery/embedding-types.d.ts +75 -0
  28. package/dist/discovery/embedding-types.d.ts.map +1 -0
  29. package/dist/discovery/embedding-types.js +1 -0
  30. package/dist/discovery/embeddings.d.ts +38 -0
  31. package/dist/discovery/embeddings.d.ts.map +1 -0
  32. package/dist/discovery/embeddings.js +71 -0
  33. package/dist/discovery/evaluation-validation.d.ts +8 -0
  34. package/dist/discovery/evaluation-validation.d.ts.map +1 -0
  35. package/dist/discovery/evaluation-validation.js +19 -0
  36. package/dist/discovery/evaluation.d.ts +84 -0
  37. package/dist/discovery/evaluation.d.ts.map +1 -0
  38. package/dist/discovery/evaluation.js +310 -0
  39. package/dist/discovery/export.d.ts +14 -0
  40. package/dist/discovery/export.d.ts.map +1 -0
  41. package/dist/discovery/export.js +81 -0
  42. package/dist/discovery/facade.d.ts +82 -0
  43. package/dist/discovery/facade.d.ts.map +1 -0
  44. package/dist/discovery/facade.js +266 -0
  45. package/dist/discovery/fingerprint.d.ts +71 -0
  46. package/dist/discovery/fingerprint.d.ts.map +1 -0
  47. package/dist/discovery/fingerprint.js +67 -0
  48. package/dist/discovery/index-builder.d.ts +3 -0
  49. package/dist/discovery/index-builder.d.ts.map +1 -0
  50. package/dist/discovery/index-builder.js +129 -0
  51. package/dist/discovery/index-validation.d.ts +12 -0
  52. package/dist/discovery/index-validation.d.ts.map +1 -0
  53. package/dist/discovery/index-validation.js +117 -0
  54. package/dist/discovery/index.d.ts +32 -0
  55. package/dist/discovery/index.d.ts.map +1 -0
  56. package/dist/discovery/index.js +18 -0
  57. package/dist/discovery/json-snapshot.d.ts +3 -0
  58. package/dist/discovery/json-snapshot.d.ts.map +1 -0
  59. package/dist/discovery/json-snapshot.js +11 -0
  60. package/dist/discovery/lexical.d.ts +12 -0
  61. package/dist/discovery/lexical.d.ts.map +1 -0
  62. package/dist/discovery/lexical.js +26 -0
  63. package/dist/discovery/metadata.d.ts +4 -0
  64. package/dist/discovery/metadata.d.ts.map +1 -0
  65. package/dist/discovery/metadata.js +33 -0
  66. package/dist/discovery/ranking.d.ts +7 -0
  67. package/dist/discovery/ranking.d.ts.map +1 -0
  68. package/dist/discovery/ranking.js +68 -0
  69. package/dist/discovery/replay.d.ts +50 -0
  70. package/dist/discovery/replay.d.ts.map +1 -0
  71. package/dist/discovery/replay.js +175 -0
  72. package/dist/discovery/retrieval-policy.d.ts +5 -0
  73. package/dist/discovery/retrieval-policy.d.ts.map +1 -0
  74. package/dist/discovery/retrieval-policy.js +10 -0
  75. package/dist/discovery/router.d.ts +39 -0
  76. package/dist/discovery/router.d.ts.map +1 -0
  77. package/dist/discovery/router.js +98 -0
  78. package/dist/discovery/selection-attempt.d.ts +20 -0
  79. package/dist/discovery/selection-attempt.d.ts.map +1 -0
  80. package/dist/discovery/selection-attempt.js +39 -0
  81. package/dist/discovery/selection-cards.d.ts +12 -0
  82. package/dist/discovery/selection-cards.d.ts.map +1 -0
  83. package/dist/discovery/selection-cards.js +79 -0
  84. package/dist/discovery/selection-configuration.d.ts +18 -0
  85. package/dist/discovery/selection-configuration.d.ts.map +1 -0
  86. package/dist/discovery/selection-configuration.js +71 -0
  87. package/dist/discovery/selection-response.d.ts +13 -0
  88. package/dist/discovery/selection-response.d.ts.map +1 -0
  89. package/dist/discovery/selection-response.js +47 -0
  90. package/dist/discovery/selection-types.d.ts +18 -0
  91. package/dist/discovery/selection-types.d.ts.map +1 -0
  92. package/dist/discovery/selection-types.js +1 -0
  93. package/dist/discovery/selection.d.ts +6 -0
  94. package/dist/discovery/selection.d.ts.map +1 -0
  95. package/dist/discovery/selection.js +145 -0
  96. package/dist/discovery/types.d.ts +291 -0
  97. package/dist/discovery/types.d.ts.map +1 -0
  98. package/dist/discovery/types.js +1 -0
  99. package/dist/discovery/usage.d.ts +2 -0
  100. package/dist/discovery/usage.d.ts.map +1 -0
  101. package/dist/discovery/usage.js +1 -0
  102. package/dist/discovery/vectors.d.ts +5 -0
  103. package/dist/discovery/vectors.d.ts.map +1 -0
  104. package/dist/discovery/vectors.js +43 -0
  105. package/dist/discovery/versions.d.ts +37 -0
  106. package/dist/discovery/versions.d.ts.map +1 -0
  107. package/dist/discovery/versions.js +36 -0
  108. package/dist/evals-live/live-eval-runner.d.ts +1 -0
  109. package/dist/evals-live/live-eval-runner.d.ts.map +1 -1
  110. package/dist/evals-live/live-eval-runner.js +12 -1
  111. package/dist/evals-live/types.d.ts +3 -0
  112. package/dist/evals-live/types.d.ts.map +1 -1
  113. package/dist/generated/manifest-types.d.ts +3655 -0
  114. package/dist/generated/manifest-types.d.ts.map +1 -1
  115. package/dist/loader.d.ts.map +1 -1
  116. package/dist/loader.js +8 -0
  117. package/dist/mcp-server/server.d.ts.map +1 -1
  118. package/dist/mcp-server/server.js +11 -2
  119. package/dist/mcp-server/types.d.ts +4 -0
  120. package/dist/mcp-server/types.d.ts.map +1 -1
  121. package/dist/observability/redaction.d.ts.map +1 -1
  122. package/dist/observability/redaction.js +6 -2
  123. package/dist/observability/types.d.ts +1 -1
  124. package/dist/observability/types.d.ts.map +1 -1
  125. package/dist/openai/index.d.ts +2 -0
  126. package/dist/openai/index.d.ts.map +1 -1
  127. package/dist/openai/index.js +1 -0
  128. package/dist/openai/responses-runner.d.ts +1 -1
  129. package/dist/openai/responses-runner.d.ts.map +1 -1
  130. package/dist/openai/responses-runner.js +134 -7
  131. package/dist/openai/selection-client.d.ts +28 -0
  132. package/dist/openai/selection-client.d.ts.map +1 -0
  133. package/dist/openai/selection-client.js +95 -0
  134. package/dist/openai/tool-loop.js +1 -1
  135. package/dist/openai/types.d.ts +19 -2
  136. package/dist/openai/types.d.ts.map +1 -1
  137. package/dist/replay/discovery-snapshot.d.ts +10 -0
  138. package/dist/replay/discovery-snapshot.d.ts.map +1 -0
  139. package/dist/replay/discovery-snapshot.js +17 -0
  140. package/dist/replay/recorder.d.ts.map +1 -1
  141. package/dist/replay/recorder.js +4 -0
  142. package/dist/replay/redaction.js +5 -1
  143. package/dist/replay/replayer.d.ts.map +1 -1
  144. package/dist/replay/replayer.js +14 -1
  145. package/dist/replay/types.d.ts +3 -0
  146. package/dist/replay/types.d.ts.map +1 -1
  147. package/dist/replay/validation.d.ts.map +1 -1
  148. package/dist/replay/validation.js +6 -0
  149. package/dist/runtime/bounded-json.d.ts +4 -0
  150. package/dist/runtime/bounded-json.d.ts.map +1 -0
  151. package/dist/runtime/bounded-json.js +74 -0
  152. package/dist/runtime/eligibility.d.ts +4 -0
  153. package/dist/runtime/eligibility.d.ts.map +1 -0
  154. package/dist/runtime/eligibility.js +128 -0
  155. package/dist/runtime/index.d.ts +5 -1
  156. package/dist/runtime/index.d.ts.map +1 -1
  157. package/dist/runtime/index.js +2 -0
  158. package/dist/runtime/provider-attempt.d.ts +11 -0
  159. package/dist/runtime/provider-attempt.d.ts.map +1 -0
  160. package/dist/runtime/provider-attempt.js +221 -0
  161. package/dist/runtime/provider-run.d.ts +10 -0
  162. package/dist/runtime/provider-run.d.ts.map +1 -0
  163. package/dist/runtime/provider-run.js +123 -0
  164. package/dist/runtime/provider-stop.d.ts +9 -0
  165. package/dist/runtime/provider-stop.d.ts.map +1 -0
  166. package/dist/runtime/provider-stop.js +28 -0
  167. package/dist/runtime/provider-types.d.ts +189 -0
  168. package/dist/runtime/provider-types.d.ts.map +1 -0
  169. package/dist/runtime/provider-types.js +1 -0
  170. package/dist/runtime/provider-usage.d.ts +9 -0
  171. package/dist/runtime/provider-usage.d.ts.map +1 -0
  172. package/dist/runtime/provider-usage.js +69 -0
  173. package/dist/runtime/router.d.ts.map +1 -1
  174. package/dist/runtime/router.js +3 -118
  175. package/dist/runtime/types.d.ts +9 -0
  176. package/dist/runtime/types.d.ts.map +1 -1
  177. package/dist/types.d.ts +1 -1
  178. package/dist/types.d.ts.map +1 -1
  179. package/dist/validator.d.ts +3 -0
  180. package/dist/validator.d.ts.map +1 -1
  181. package/dist/validator.js +32 -0
  182. package/docs/api/public-api-policy.md +10 -0
  183. package/docs/api/stable-workflow.md +58 -0
  184. package/docs/api.md +7 -0
  185. package/docs/capability-manifests.md +24 -0
  186. package/docs/development.md +22 -0
  187. package/docs/discovery.md +186 -0
  188. package/docs/evals/discovery-live.md +199 -0
  189. package/docs/evals/discovery-qualification.md +158 -0
  190. package/docs/evals/overview.md +9 -0
  191. package/docs/getting-started/installation.md +11 -12
  192. package/docs/getting-started/openai-quickstart.md +16 -5
  193. package/docs/getting-started/provider-neutral-quickstart.md +247 -88
  194. package/docs/getting-started/quickstart.md +8 -30
  195. package/docs/index.md +10 -0
  196. package/docs/release.md +29 -5
  197. package/docs/runtime.md +27 -0
  198. package/docs/start-here.md +23 -134
  199. package/examples/12-capability-discovery-selection/README.md +346 -0
  200. package/examples/12-capability-discovery-selection/customer-cases.mjs +52 -0
  201. package/examples/12-capability-discovery-selection/customer-harness.mjs +91 -0
  202. package/examples/12-capability-discovery-selection/customer-request-example.mjs +320 -0
  203. package/examples/12-capability-discovery-selection/discovery-example.mjs +175 -0
  204. package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.suite.json +7887 -0
  205. package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v2.suite.json +8167 -0
  206. package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v3.suite.json +8342 -0
  207. package/examples/12-capability-discovery-selection/evaluation/application.mjs +305 -0
  208. package/examples/12-capability-discovery-selection/evaluation/campaign.mjs +248 -0
  209. package/examples/12-capability-discovery-selection/evaluation/clients.mjs +433 -0
  210. package/examples/12-capability-discovery-selection/evaluation/continuation.mjs +208 -0
  211. package/examples/12-capability-discovery-selection/evaluation/durable-budget.mjs +420 -0
  212. package/examples/12-capability-discovery-selection/evaluation/freeze.mjs +9 -0
  213. package/examples/12-capability-discovery-selection/evaluation/harness.mjs +618 -0
  214. package/examples/12-capability-discovery-selection/evaluation/mock-host.mjs +260 -0
  215. package/examples/12-capability-discovery-selection/evaluation/preflight.mjs +115 -0
  216. package/examples/12-capability-discovery-selection/evaluation/qualification-workflows.mjs +87 -0
  217. package/examples/12-capability-discovery-selection/evaluation/regrade.mjs +281 -0
  218. package/examples/12-capability-discovery-selection/evaluation/report.mjs +517 -0
  219. package/examples/12-capability-discovery-selection/evaluation/runner.mjs +613 -0
  220. package/examples/12-capability-discovery-selection/evaluation/saved-campaign.mjs +385 -0
  221. package/examples/12-capability-discovery-selection/evaluation/wording.mjs +115 -0
  222. package/examples/12-capability-discovery-selection/evaluation/workflow-application.mjs +378 -0
  223. package/examples/12-capability-discovery-selection/evaluation/workflow-cases.mjs +90 -0
  224. package/examples/12-capability-discovery-selection/evaluation-example.mjs +154 -0
  225. package/examples/12-capability-discovery-selection/hybrid-example.mjs +174 -0
  226. package/examples/12-capability-discovery-selection/mock-embeddings.mjs +101 -0
  227. package/examples/12-capability-discovery-selection/mock-selection.mjs +78 -0
  228. package/examples/12-capability-discovery-selection/pipeline-example.mjs +496 -0
  229. package/examples/12-capability-discovery-selection/retrieval-cases.mjs +31 -0
  230. package/examples/12-capability-discovery-selection/run-campaign-mock.mjs +40 -0
  231. package/examples/12-capability-discovery-selection/run-customer-mock.mjs +15 -0
  232. package/examples/12-capability-discovery-selection/run-evaluation-live.mjs +10 -0
  233. package/examples/12-capability-discovery-selection/run-evaluation-mock.mjs +3 -0
  234. package/examples/12-capability-discovery-selection/run-evaluation-regrade.mjs +71 -0
  235. package/examples/12-capability-discovery-selection/run-hybrid-mock.mjs +3 -0
  236. package/examples/12-capability-discovery-selection/run-mock.mjs +3 -0
  237. package/examples/12-capability-discovery-selection/run-pipeline-mock.mjs +2 -0
  238. package/examples/12-capability-discovery-selection/run-selector-mock.mjs +3 -0
  239. package/examples/12-capability-discovery-selection/selection-example.mjs +88 -0
  240. package/package.json +17 -4
  241. package/schemas/discovery/embedding-index.schema.json +71 -0
  242. package/schemas/discovery/eval-observations.schema.json +409 -0
  243. package/schemas/discovery/eval-suite.schema.json +375 -0
  244. package/schemas/discovery/metadata.schema.json +30 -0
  245. package/schemas/discovery/selection-response.schema.json +47 -0
  246. package/schemas/replay/discovery-snapshot.schema.json +335 -0
@@ -0,0 +1,496 @@
1
+ import assert from "node:assert/strict";
2
+ import { toOpenAIResponsesToolName } from "ai-capability-framework";
3
+ import {
4
+ createAicfProviderRun,
5
+ DefaultContextBuilder
6
+ } from "ai-capability-framework/runtime";
7
+ import {
8
+ createCapabilityDiscovery,
9
+ createInMemoryDiscoveryBudget,
10
+ buildCapabilityEmbeddingIndex,
11
+ buildCapabilitySearchCorpus,
12
+ createDiscoverySnapshot,
13
+ replayDiscoverySnapshot
14
+ } from "ai-capability-framework/discovery";
15
+ import {
16
+ createOpenAISelectionClient,
17
+ MockOpenAIResponsesClient,
18
+ mockTextResponse,
19
+ mockFunctionCallResponse,
20
+ runOpenAIResponses
21
+ } from "ai-capability-framework/openai";
22
+ import { createCustomerHarness } from "./customer-harness.mjs";
23
+ import {
24
+ plannedTasks,
25
+ taskExpectations,
26
+ validatePlannedTasks
27
+ } from "./customer-cases.mjs";
28
+ import {
29
+ DeterministicMockEmbeddingClient,
30
+ embeddingIndexLimits
31
+ } from "./mock-embeddings.mjs";
32
+
33
+ const configuration = {
34
+ provider: "openai",
35
+ model: "mock-model",
36
+ configurationId: "example.pipeline.responses.v1"
37
+ };
38
+ const normalizedUsage = {
39
+ inputTokens: 20,
40
+ outputTokens: 5,
41
+ totalTokens: 25,
42
+ cachedInputTokens: 0,
43
+ reasoningTokens: 0,
44
+ estimatedCost: 0
45
+ };
46
+ function reported(response) {
47
+ return {
48
+ ...response,
49
+ status: "completed",
50
+ usage: {
51
+ input_tokens: 20,
52
+ output_tokens: 5,
53
+ total_tokens: 25,
54
+ input_tokens_details: { cached_tokens: 0 },
55
+ output_tokens_details: { reasoning_tokens: 0 }
56
+ }
57
+ };
58
+ }
59
+ function runFor(harness, maxProviderCalls = 20) {
60
+ const budget = createInMemoryDiscoveryBudget({
61
+ runId: harness.runtimeContext.runId,
62
+ maxProviderCalls,
63
+ maxInputTokens: 80_000,
64
+ maxOutputTokens: 20_000
65
+ });
66
+ return createAicfProviderRun({
67
+ runId: harness.runtimeContext.runId,
68
+ budget,
69
+ deadlineAt: Date.now() + 30_000
70
+ });
71
+ }
72
+ // An application explicitly invokes this administrative operation, separately from serving.
73
+ async function buildMockIndex(harness) {
74
+ const client = new DeterministicMockEmbeddingClient();
75
+ const budget = createInMemoryDiscoveryBudget({
76
+ runId: "run_index_example",
77
+ maxProviderCalls: 1,
78
+ maxInputTokens: 8000
79
+ });
80
+ const execution = {
81
+ budgetPolicy: budget.policy,
82
+ limits: {
83
+ maxInputTokens: 8000,
84
+ maxCharactersPerText: 16_384,
85
+ maxCharactersPerCall: 65_536
86
+ }
87
+ };
88
+ const built = await buildCapabilityEmbeddingIndex({
89
+ corpus: buildCapabilitySearchCorpus(harness.registry),
90
+ client,
91
+ execution,
92
+ limits: embeddingIndexLimits,
93
+ batchSize: 10,
94
+ maxProviderCalls: 1,
95
+ budget: budget.ledger,
96
+ runId: "run_index_example",
97
+ requestId: "index_build_example",
98
+ deadlineAt: Date.now() + 30_000
99
+ });
100
+ assert.equal(built.status, "built");
101
+ return { client, index: built.index, usage: built.usage };
102
+ }
103
+
104
+ /** Four explicit integrations; all responses and vectors are deterministic contract fixtures. */
105
+ export async function runPipelineCase({ hybrid = false, dedicated = false } = {}) {
106
+ const harness = await createCustomerHarness(),
107
+ providerRun = runFor(harness);
108
+ const administrative = hybrid ? await buildMockIndex(harness) : undefined;
109
+ const retrieval = {
110
+ policyVersion: "example.pipeline.v1",
111
+ ...(hybrid
112
+ ? {
113
+ mode: "hybrid",
114
+ weights: { semantic: 0.75, lexical: 0.25 },
115
+ failurePolicy: "unavailable"
116
+ }
117
+ : { mode: "lexical" })
118
+ };
119
+ const discoveryOptions = {
120
+ registry: harness.registry,
121
+ ...retrieval,
122
+ ...(hybrid
123
+ ? {
124
+ embedding: {
125
+ client: administrative.client,
126
+ index: administrative.index,
127
+ maxInputTokens: 4000
128
+ }
129
+ }
130
+ : {})
131
+ };
132
+ const planning = await providerRun.attempt({
133
+ requestId: "request_plan",
134
+ stage: "planning",
135
+ configuration,
136
+ mock: true,
137
+ maxInputTokens: 4000,
138
+ maxOutputTokens: 1000,
139
+ invoke: async () => ({
140
+ output: plannedTasks,
141
+ providerAttempts: 1,
142
+ usage: normalizedUsage
143
+ }),
144
+ validate: (response) => ({ value: validatePlannedTasks(response.output) })
145
+ });
146
+ assert.equal(planning.code, undefined);
147
+ const completed = new Map(),
148
+ taskResults = [],
149
+ snapshots = [];
150
+ let ordinaryTurns = 0,
151
+ selectorAttempts = 0;
152
+ for (const task of planning.value) {
153
+ assert.ok(task.dependsOn.every((id) => completed.has(id)));
154
+ const prior = completed.get("read-ticket");
155
+ const runtimeContext = {
156
+ ...harness.runtimeContext,
157
+ requestId: "request_" + task.id
158
+ };
159
+ const contextBuilder = new DefaultContextBuilder({
160
+ hostItems: prior
161
+ ? [
162
+ {
163
+ id: "authorized-ticket",
164
+ kind: "entity",
165
+ title: "Authorized ticket result",
166
+ data: prior.data,
167
+ trusted: true,
168
+ visibleToModel: true
169
+ }
170
+ ]
171
+ : []
172
+ });
173
+ const discovery = createCapabilityDiscovery({
174
+ ...discoveryOptions,
175
+ contextBuilder
176
+ });
177
+ const [result] = await discovery.search({
178
+ tasks: [{ id: task.id, text: task.text }],
179
+ runtimeContext,
180
+ providerRun
181
+ });
182
+ assert.equal(result.status, "matched");
183
+ const expectations = taskExpectations[task.id],
184
+ isRead = task.id === "read-ticket";
185
+ const capabilityId = isRead ? "support.ticket.get" : "support.refund.prepare_case";
186
+ assert.ok(
187
+ result.candidates.some((candidate) =>
188
+ expectations.compatibleIds.includes(candidate.capabilityId)
189
+ )
190
+ );
191
+ assert.ok(
192
+ result.candidates.every(
193
+ (candidate) => !expectations.forbiddenIds.includes(candidate.capabilityId)
194
+ )
195
+ );
196
+ let batch;
197
+ if (dedicated) {
198
+ const transport = new MockOpenAIResponsesClient([
199
+ reported(
200
+ mockTextResponse(
201
+ JSON.stringify({
202
+ schema_version: "1.0",
203
+ decisions: [
204
+ {
205
+ query_id: task.id,
206
+ decision: "select",
207
+ capability_id: capabilityId,
208
+ reason_code: "supported_candidate"
209
+ }
210
+ ]
211
+ })
212
+ )
213
+ )
214
+ ]);
215
+ const client = createOpenAISelectionClient({
216
+ client: transport,
217
+ configuration,
218
+ mock: true,
219
+ inputTokenPreflight: () => 120
220
+ });
221
+ batch = await discovery.select({
222
+ results: [result],
223
+ client,
224
+ maxInputTokens: 4000,
225
+ maxOutputTokens: 1000,
226
+ estimateCost: () => 0
227
+ });
228
+ assert.equal(batch.outcomes[0].status, "selected");
229
+ selectorAttempts += transport.requests.length;
230
+ }
231
+ const router = batch
232
+ ? discovery.routerFor(batch, task.id)
233
+ : discovery.routerFor(result);
234
+ const client = new MockOpenAIResponsesClient([
235
+ reported(
236
+ mockFunctionCallResponse({
237
+ name: toOpenAIResponsesToolName(capabilityId),
238
+ callId: "call_" + task.id,
239
+ args: isRead
240
+ ? { ticket_id: "TCK-100" }
241
+ : {
242
+ ticket_id: prior.data.ticket.ticket_id,
243
+ order_id: prior.data.ticket.order_id,
244
+ reason_code: "customer_request",
245
+ requested_amount: null
246
+ }
247
+ })
248
+ ),
249
+ reported(
250
+ mockTextResponse(
251
+ isRead ? "Ticket read." : "Refund preparation requires approval."
252
+ )
253
+ )
254
+ ]);
255
+ const loop = await runOpenAIResponses({
256
+ ...harness,
257
+ runtimeContext,
258
+ contextBuilder,
259
+ userInput: { text: task.text },
260
+ router,
261
+ client,
262
+ model: "mock-model",
263
+ providerExecution: {
264
+ configuration,
265
+ mock: true,
266
+ maxInputTokens: 4000,
267
+ maxOutputTokens: 1000,
268
+ inputTokenPreflight: () => 120,
269
+ estimateCost: () => 0
270
+ }
271
+ });
272
+ assert.equal(loop.status, "completed");
273
+ assert.ok(
274
+ loop.toolCalls.every((call) =>
275
+ expectations.compatibleIds.includes(call.capabilityId)
276
+ )
277
+ );
278
+ assert.ok(
279
+ client.requests.every((request) =>
280
+ request.tools.every(
281
+ (tool) =>
282
+ tool.name !== toOpenAIResponsesToolName("support.refund.commit_case")
283
+ )
284
+ )
285
+ );
286
+ completed.set(task.id, loop.toolResults[0]);
287
+ ordinaryTurns += client.requests.length;
288
+ taskResults.push({
289
+ taskId: task.id,
290
+ candidateIds: result.candidates.map((candidate) => candidate.capabilityId),
291
+ chosenId: loop.toolCalls[0].capabilityId,
292
+ toolStatus: loop.toolResults[0].status,
293
+ approvalRequired:
294
+ loop.toolResults[0].action?.approvalRequired ??
295
+ loop.toolResults[0].status === "approval_required"
296
+ });
297
+ const builtContext = await contextBuilder.build({
298
+ baseContext: runtimeContext,
299
+ registry: harness.registry,
300
+ userInput: { text: task.text }
301
+ });
302
+ const routeRequest = {
303
+ registry: harness.registry,
304
+ builtContext,
305
+ userInput: { text: task.text }
306
+ };
307
+ const snapshot = createDiscoverySnapshot({
308
+ routeRequest,
309
+ configuration: retrieval,
310
+ queries: [{ id: task.id, fragments: [{ key: "task", text: task.text }] }],
311
+ discovery: [result],
312
+ selection: batch
313
+ });
314
+ assert.equal(
315
+ replayDiscoverySnapshot(snapshot, { mode: "snapshot_validation", routeRequest })
316
+ .status,
317
+ "passed"
318
+ );
319
+ assert.equal(
320
+ replayDiscoverySnapshot(snapshot, { mode: "ranking", routeRequest }).status,
321
+ "refused"
322
+ );
323
+ snapshots.push(snapshot.snapshotFingerprint);
324
+ }
325
+ assert.deepEqual(harness.executedOperations, ["read", "prepare"]);
326
+ assert.equal(harness.readCalls.length, 1);
327
+ assert.equal(harness.prepareCalls.length, 1);
328
+ assert.equal(
329
+ harness.prepareCalls[0].order_id,
330
+ completed.get("read-ticket").data.ticket.order_id
331
+ );
332
+ assert.ok(completed.get("prepare-refund").action?.preparedActionId);
333
+ const receipt = providerRun.snapshot();
334
+ const queryEmbeddings = hybrid
335
+ ? administrative.client.calls.length - administrative.usage.totals.providerAttempts
336
+ : 0;
337
+ assert.equal(
338
+ receipt.usage.totals.providerAttempts,
339
+ 1 + ordinaryTurns + selectorAttempts + queryEmbeddings
340
+ );
341
+ assert.equal(ordinaryTurns, 4);
342
+ assert.equal(selectorAttempts, dedicated ? 2 : 0);
343
+ assert.equal(queryEmbeddings, hybrid ? 2 : 0);
344
+ return {
345
+ retrieval: hybrid ? "hybrid" : "lexical",
346
+ selection: dedicated ? "dedicated" : "native",
347
+ mock: true,
348
+ planningTurns: 1,
349
+ ordinaryTurns,
350
+ selectorAttempts,
351
+ queryEmbeddings,
352
+ administrativeIndexUsage: administrative?.usage ?? null,
353
+ tasks: taskResults,
354
+ snapshotFingerprints: snapshots,
355
+ usage: receipt.usage,
356
+ traceEvents: receipt.events.length,
357
+ customerResponse:
358
+ "Ticket TCK-100 was read. A refund case for order ORD-100 is prepared and requires approval; no refund was committed."
359
+ };
360
+ }
361
+
362
+ export async function runPipelineFailureCases() {
363
+ const harness = await createCustomerHarness(),
364
+ providerRun = runFor(harness);
365
+ const discovery = createCapabilityDiscovery({ registry: harness.registry });
366
+ const [none] = await discovery.search({
367
+ tasks: [{ id: "none", text: "quasar" }],
368
+ runtimeContext: harness.runtimeContext,
369
+ providerRun
370
+ });
371
+ assert.equal(none.status, "no_match");
372
+ const [matched] = await discovery.search({
373
+ tasks: [{ id: "one", text: "ticket" }],
374
+ runtimeContext: harness.runtimeContext,
375
+ providerRun
376
+ });
377
+ const transport = new MockOpenAIResponsesClient([
378
+ reported(
379
+ mockTextResponse(
380
+ JSON.stringify({
381
+ schema_version: "1.0",
382
+ decisions: [
383
+ {
384
+ query_id: "one",
385
+ decision: "abstain",
386
+ capability_id: null,
387
+ reason_code: "insufficient_context"
388
+ }
389
+ ]
390
+ })
391
+ )
392
+ )
393
+ ]);
394
+ const batch = await discovery.select({
395
+ results: [matched],
396
+ client: createOpenAISelectionClient({
397
+ client: transport,
398
+ configuration,
399
+ mock: true,
400
+ inputTokenPreflight: () => 120
401
+ }),
402
+ maxInputTokens: 4000,
403
+ maxOutputTokens: 1000
404
+ });
405
+ assert.equal(batch.outcomes[0].status, "abstained");
406
+ const bound = discovery.routerFor(matched);
407
+ harness.runtimeContext.subject.permissions = [];
408
+ const builtContext = await new DefaultContextBuilder().build({
409
+ baseContext: harness.runtimeContext,
410
+ registry: harness.registry,
411
+ userInput: { text: "ticket" }
412
+ });
413
+ assert.equal(
414
+ bound.route({
415
+ registry: harness.registry,
416
+ builtContext,
417
+ userInput: { text: "ticket" }
418
+ }).items.length,
419
+ 0
420
+ );
421
+ const fresh = await createCustomerHarness(),
422
+ administrative = await buildMockIndex(fresh),
423
+ invalidRun = runFor(fresh);
424
+ const before = administrative.client.calls.length;
425
+ const invalid = createCapabilityDiscovery({
426
+ registry: fresh.registry,
427
+ mode: "hybrid",
428
+ embedding: {
429
+ client: administrative.client,
430
+ index: { ...administrative.index, indexFingerprint: "sha256:" + "0".repeat(64) },
431
+ maxInputTokens: 4000
432
+ }
433
+ });
434
+ const [rejected] = await invalid.search({
435
+ tasks: [{ id: "bad", text: "ticket" }],
436
+ runtimeContext: fresh.runtimeContext,
437
+ providerRun: invalidRun
438
+ });
439
+ assert.equal(rejected.status, "unavailable");
440
+ assert.equal(administrative.client.calls.length, before);
441
+ const sourceApi = createCapabilityDiscovery({ registry: fresh.registry });
442
+ const [sourceResult] = await sourceApi.search({
443
+ tasks: [{ id: "source", text: "ticket" }],
444
+ runtimeContext: fresh.runtimeContext
445
+ });
446
+ fresh.registry.capabilities[0].manifest.summary += " Updated guidance.";
447
+ const sourceContext = await new DefaultContextBuilder().build({
448
+ baseContext: fresh.runtimeContext,
449
+ registry: fresh.registry,
450
+ userInput: { text: "ticket" }
451
+ });
452
+ assert.equal(
453
+ sourceApi.routerFor(sourceResult).route({
454
+ registry: fresh.registry,
455
+ builtContext: sourceContext,
456
+ userInput: { text: "ticket" }
457
+ }).items.length,
458
+ 0
459
+ );
460
+ const exhausted = runFor(fresh, 1);
461
+ const attempt = {
462
+ requestId: "budget",
463
+ stage: "planning",
464
+ configuration,
465
+ mock: true,
466
+ maxInputTokens: 100,
467
+ maxOutputTokens: 20,
468
+ invoke: async () => ({ providerAttempts: 1, usage: normalizedUsage }),
469
+ validate: () => ({ value: true })
470
+ };
471
+ await exhausted.attempt(attempt);
472
+ assert.equal((await exhausted.attempt(attempt)).code, "budget_denied");
473
+ return {
474
+ clarification: "Please clarify the task you want to perform.",
475
+ noMatch: none.status,
476
+ abstention: batch.outcomes[0].status,
477
+ permissionRevocation: "no_tools",
478
+ sourceMutation: "no_tools",
479
+ invalidIndex: rejected.status,
480
+ invalidIndexQueryCalls: administrative.client.calls.length - before,
481
+ budgetDeniedAfterAttempts: exhausted.snapshot().usage.totals.providerAttempts,
482
+ realProviderCalls: 0
483
+ };
484
+ }
485
+ export async function runPipelineExample() {
486
+ const cases = [];
487
+ for (const hybrid of [false, true])
488
+ for (const dedicated of [false, true])
489
+ cases.push(await runPipelineCase({ hybrid, dedicated }));
490
+ return {
491
+ cases,
492
+ failures: await runPipelineFailureCases(),
493
+ realProviderCalls: 0,
494
+ qualityQualified: false
495
+ };
496
+ }
@@ -0,0 +1,31 @@
1
+ // Executable retrieval fixtures reuse the support manifests without adding manifest files.
2
+ export const retrievalCases = [
3
+ {
4
+ id: "ticket_lookup",
5
+ fragments: [{ key: "request", text: "Read ticket" }],
6
+ compatibleIds: ["support.ticket.get"],
7
+ forbiddenIds: ["support.refund.commit_case", "support.refund.prepare_case"]
8
+ },
9
+ {
10
+ id: "refund_review",
11
+ fragments: [{ key: "request", text: "Prepare refund" }],
12
+ compatibleIds: ["support.refund.prepare_case"],
13
+ forbiddenIds: ["support.refund.commit_case", "support.ticket.get"]
14
+ },
15
+ {
16
+ id: "ticket_lookup_again",
17
+ fragments: [{ key: "request", text: "ticket" }],
18
+ compatibleIds: ["support.ticket.get"],
19
+ forbiddenIds: ["support.refund.commit_case"]
20
+ },
21
+ {
22
+ id: "no_matching_terms",
23
+ fragments: [{ key: "request", text: "quasar" }],
24
+ compatibleIds: [],
25
+ forbiddenIds: [
26
+ "support.refund.commit_case",
27
+ "support.refund.prepare_case",
28
+ "support.ticket.get"
29
+ ]
30
+ }
31
+ ];
@@ -0,0 +1,40 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { writeFileSync } from "node:fs";
4
+ import { runCampaign } from "./evaluation/runner.mjs";
5
+ import { createMockHost } from "./evaluation/mock-host.mjs";
6
+
7
+ const directory = path.resolve(".aicf/discovery-evaluation", `mock-${randomUUID()}`);
8
+ const result = await runCampaign({ directory, mockFactory: createMockHost });
9
+ writeFileSync(
10
+ path.join(directory, "report.json"),
11
+ JSON.stringify(result, null, 2) + "\n"
12
+ );
13
+ if (
14
+ !result.report?.complete ||
15
+ result.report.ledger.pendingReservations ||
16
+ result.report.semanticQualityQualified ||
17
+ Object.values(result.report.modes).some((mode) => !mode.workflow.qualified)
18
+ )
19
+ throw new Error("Mock campaign integration did not satisfy its offline contract.");
20
+ console.log(
21
+ JSON.stringify(
22
+ {
23
+ status: result.status,
24
+ liveProviderCalls: 0,
25
+ campaignFingerprint: result.campaignFingerprint,
26
+ indexFingerprint: result.indexFingerprint,
27
+ ledger: result.report.ledger,
28
+ workflows: Object.fromEntries(
29
+ Object.entries(result.report.modes).map(([mode, value]) => [
30
+ mode,
31
+ value.workflow
32
+ ])
33
+ ),
34
+ evidenceDirectory: directory,
35
+ semanticQualityQualified: false
36
+ },
37
+ null,
38
+ 2
39
+ )
40
+ );
@@ -0,0 +1,15 @@
1
+ import {
2
+ runCustomerRequestExample,
3
+ runSimpleHybridExample
4
+ } from "./customer-request-example.mjs";
5
+
6
+ console.log(
7
+ JSON.stringify(
8
+ {
9
+ customerFlow: await runCustomerRequestExample(),
10
+ hybrid: await runSimpleHybridExample()
11
+ },
12
+ null,
13
+ 2
14
+ )
15
+ );
@@ -0,0 +1,10 @@
1
+ import { campaignCli } from "./evaluation/runner.mjs";
2
+
3
+ try {
4
+ console.log(JSON.stringify(await campaignCli(process.argv.slice(2)), null, 2));
5
+ } catch {
6
+ console.error(
7
+ "Evaluation could not run. Check the explicit configuration, offline gate, campaign lock and fingerprinted evidence; no automatic retry was scheduled."
8
+ );
9
+ process.exitCode = 1;
10
+ }
@@ -0,0 +1,3 @@
1
+ import { runDiscoveryEvaluationExample } from "./evaluation-example.mjs";
2
+
3
+ console.log(JSON.stringify(await runDiscoveryEvaluationExample(), null, 2));
@@ -0,0 +1,71 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { regradeSavedCampaign } from "./evaluation/regrade.mjs";
6
+
7
+ export function writeOfflineRegrade(inputPath, outputPath) {
8
+ const input = fs.realpathSync(inputPath),
9
+ output = path.resolve(outputPath);
10
+ const parent = fs.realpathSync(path.dirname(output));
11
+ if (path.dirname(output) !== parent)
12
+ throw new Error("Output parent must be a real directory.");
13
+ execFileSync("git", ["check-ignore", "--quiet", "--", output], { stdio: "ignore" });
14
+ const { value, evidence } = regradeSavedCampaign(input);
15
+ const inside = (target, directory) => {
16
+ const relative = path.relative(directory, target);
17
+ return (
18
+ relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
19
+ );
20
+ };
21
+ if (
22
+ inside(output, evidence.directory) ||
23
+ inside(evidence.directory, output) ||
24
+ [evidence.reportPath, input].some((source) => inside(source, output))
25
+ )
26
+ throw new Error("Output must be separate from source evidence and input files.");
27
+ const serialized = JSON.stringify(value, null, 2) + "\n";
28
+ if (Buffer.byteLength(serialized) > 16 * 1024 * 1024)
29
+ throw new Error("Regrade report exceeds its bound.");
30
+ const name = "regrade.json",
31
+ target = path.join(output, name);
32
+ if (fs.existsSync(output)) {
33
+ if (
34
+ fs.lstatSync(output).isSymbolicLink() ||
35
+ fs.readdirSync(output).join() !== name ||
36
+ fs.readFileSync(target, "utf8") !== serialized
37
+ )
38
+ throw new Error("Conflicting output already exists.");
39
+ } else {
40
+ const temporary = fs.mkdtempSync(output + ".pending-");
41
+ try {
42
+ fs.writeFileSync(path.join(temporary, name), serialized, { flag: "wx" });
43
+ fs.renameSync(temporary, output);
44
+ } finally {
45
+ fs.rmSync(temporary, { recursive: true, force: true });
46
+ }
47
+ }
48
+ return {
49
+ reportFingerprint: value.reportFingerprint,
50
+ original: value.original,
51
+ revised: value.revised,
52
+ executionUsage: value.executionUsage
53
+ };
54
+ }
55
+
56
+ if (
57
+ process.argv[1] &&
58
+ path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
59
+ ) {
60
+ try {
61
+ const args = process.argv.slice(2);
62
+ if (args.length !== 4 || args[0] !== "--input" || args[2] !== "--output")
63
+ throw new Error("Explicit input and output required.");
64
+ console.log(JSON.stringify(writeOfflineRegrade(args[1], args[3]), null, 2));
65
+ } catch {
66
+ console.error(
67
+ "Offline regrade refused: check pinned evidence, complete workflow facts and a separate ignored output directory. No provider work was attempted."
68
+ );
69
+ process.exitCode = 1;
70
+ }
71
+ }
@@ -0,0 +1,3 @@
1
+ import { runHybridExample } from "./hybrid-example.mjs";
2
+
3
+ console.log(JSON.stringify(await runHybridExample(), null, 2));
@@ -0,0 +1,3 @@
1
+ import { runDiscoveryExample } from "./discovery-example.mjs";
2
+
3
+ console.log(JSON.stringify(await runDiscoveryExample(), null, 2));
@@ -0,0 +1,2 @@
1
+ import { runPipelineExample } from "./pipeline-example.mjs";
2
+ console.log(JSON.stringify(await runPipelineExample(), null, 2));
@@ -0,0 +1,3 @@
1
+ import { runSelectionExample } from "./selection-example.mjs";
2
+
3
+ console.log(JSON.stringify(await runSelectionExample(), null, 2));