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,320 @@
1
+ import { toOpenAIResponsesToolName } from "ai-capability-framework";
2
+ import { DefaultContextBuilder } from "ai-capability-framework/runtime";
3
+ import {
4
+ createCapabilityDiscovery,
5
+ createInMemoryDiscoveryBudget,
6
+ buildCapabilitySearchCorpus,
7
+ buildCapabilityEmbeddingIndex,
8
+ DISCOVERY_STANDARD_PROFILE,
9
+ aggregateDiscoveryUsage
10
+ } from "ai-capability-framework/discovery";
11
+ import {
12
+ MockOpenAIResponsesClient,
13
+ mockFunctionCallResponse,
14
+ mockTextResponse,
15
+ runOpenAIResponses
16
+ } from "ai-capability-framework/openai";
17
+ import {
18
+ customerRequest,
19
+ plannedTasks,
20
+ taskExpectations,
21
+ validatePlannedTasks
22
+ } from "./customer-cases.mjs";
23
+ import { createCustomerHarness } from "./customer-harness.mjs";
24
+ import { DeterministicMockEmbeddingClient } from "./mock-embeddings.mjs";
25
+
26
+ function mockModel(responses) {
27
+ const client = new MockOpenAIResponsesClient(
28
+ responses.map((response) => ({
29
+ ...response,
30
+ usage: { input_tokens: 20, output_tokens: 5, total_tokens: 25 }
31
+ }))
32
+ );
33
+ const usage = [];
34
+ const create = client.responses.create;
35
+ client.responses.create = async (input) => {
36
+ const response = await create(input);
37
+ usage.push(response.usage);
38
+ return response;
39
+ };
40
+ return { client, usage };
41
+ }
42
+
43
+ function modelUsage(reports) {
44
+ return {
45
+ mock: true,
46
+ inputTokens: reports.reduce((sum, report) => sum + report.input_tokens, 0),
47
+ outputTokens: reports.reduce((sum, report) => sum + report.output_tokens, 0),
48
+ totalTokens: reports.reduce((sum, report) => sum + report.total_tokens, 0),
49
+ tokenTotalsComplete: true,
50
+ estimatedCost: null,
51
+ costComplete: false
52
+ };
53
+ }
54
+
55
+ export async function runCustomerRequestExample() {
56
+ const harness = await createCustomerHarness();
57
+ const planning = mockModel([
58
+ mockTextResponse(JSON.stringify(plannedTasks), "plan_example")
59
+ ]);
60
+ const planResponse = await planning.client.responses.create({
61
+ model: "mock-model",
62
+ input: customerRequest,
63
+ instructions:
64
+ "Return ordered task objects with id, text and dependsOn. Use prior tasks for dependencies."
65
+ });
66
+ if (
67
+ typeof planResponse.output_text !== "string" ||
68
+ planResponse.output_text.length > 32_000
69
+ )
70
+ throw new Error("Invalid application task plan.");
71
+ const tasks = validatePlannedTasks(JSON.parse(planResponse.output_text));
72
+ const discovery = createCapabilityDiscovery({ registry: harness.registry });
73
+ const completed = new Map();
74
+ const taskResults = [],
75
+ ordinaryUsage = [];
76
+ let ordinaryModelTurns = 0;
77
+
78
+ // Application orchestration: discover one task when its dependencies are available.
79
+ for (const task of tasks) {
80
+ if (task.dependsOn.some((id) => !completed.has(id)))
81
+ throw new Error("Task dependency is not complete.");
82
+ const prior = completed.get("read-ticket");
83
+ const runtimeContext = {
84
+ ...harness.runtimeContext,
85
+ requestId: "request_" + task.id
86
+ };
87
+ const contextBuilder = new DefaultContextBuilder({
88
+ hostItems: prior
89
+ ? [
90
+ {
91
+ id: "ticket-result",
92
+ kind: "entity",
93
+ title: "Authorized ticket result",
94
+ data: prior.data,
95
+ trusted: true,
96
+ visibleToModel: true
97
+ }
98
+ ]
99
+ : []
100
+ });
101
+ const [result] = await discovery.search({
102
+ tasks: [{ id: task.id, text: task.text }],
103
+ runtimeContext
104
+ });
105
+ if (result.status !== "matched")
106
+ throw new Error("Expected a usable shortlist for the synthetic task.");
107
+ const expected = taskExpectations[task.id];
108
+ if (
109
+ result.candidates.some((candidate) =>
110
+ expected.forbiddenIds.includes(candidate.capabilityId)
111
+ )
112
+ )
113
+ throw new Error("Forbidden capability in shortlist.");
114
+ const isRead = task.id === "read-ticket";
115
+ const capabilityId = isRead ? "support.ticket.get" : "support.refund.prepare_case";
116
+ const model = mockModel([
117
+ mockFunctionCallResponse({
118
+ name: toOpenAIResponsesToolName(capabilityId),
119
+ callId: "call_" + task.id,
120
+ args: isRead
121
+ ? { ticket_id: "TCK-100" }
122
+ : {
123
+ ticket_id: prior.data.ticket.ticket_id,
124
+ order_id: prior.data.ticket.order_id,
125
+ reason_code: "customer_request",
126
+ requested_amount: null
127
+ }
128
+ }),
129
+ mockTextResponse(
130
+ isRead
131
+ ? "Ticket TCK-100 is open for order ORD-100."
132
+ : "The refund case is prepared and awaiting approval."
133
+ )
134
+ ]);
135
+ const loop = await runOpenAIResponses({
136
+ ...harness,
137
+ runtimeContext,
138
+ contextBuilder,
139
+ userInput: { text: task.text },
140
+ router: discovery.routerFor(result),
141
+ client: model.client,
142
+ model: "mock-model"
143
+ });
144
+ const outcome = loop.toolResults[0];
145
+ if (
146
+ loop.status !== "completed" ||
147
+ !outcome ||
148
+ !["success", "prepared", "approval_required"].includes(outcome.status) ||
149
+ !loop.toolCalls.every((call) =>
150
+ expected.compatibleIds.includes(call.capabilityId)
151
+ )
152
+ )
153
+ throw new Error(
154
+ "Unexpected tool outcome: " +
155
+ JSON.stringify({
156
+ taskId: task.id,
157
+ status: loop.status,
158
+ errors: loop.errors,
159
+ toolStatus: outcome?.status,
160
+ toolErrors: outcome?.errors
161
+ })
162
+ );
163
+ const exportedToolNames = model.client.requests[0].tools.map((tool) => tool.name);
164
+ if (
165
+ exportedToolNames.includes(
166
+ toOpenAIResponsesToolName("support.refund.commit_case")
167
+ )
168
+ )
169
+ throw new Error("Commit must not be exported.");
170
+ completed.set(task.id, outcome);
171
+ ordinaryModelTurns += model.client.requests.length;
172
+ ordinaryUsage.push(...model.usage);
173
+ taskResults.push({
174
+ taskId: task.id,
175
+ candidateIds: result.candidates.map((candidate) => candidate.capabilityId),
176
+ exportedToolNames,
177
+ selectedCapabilityId: loop.toolCalls[0].capabilityId,
178
+ toolStatus: outcome.status,
179
+ approvalRequired:
180
+ outcome.action?.approvalRequired ?? outcome.status === "approval_required",
181
+ preview: outcome.data?.preview,
182
+ discoveryUsage: result.usage
183
+ });
184
+ }
185
+ if (
186
+ harness.readCalls.length !== 1 ||
187
+ harness.prepareCalls.length !== 1 ||
188
+ harness.prepareCalls[0].order_id !==
189
+ completed.get("read-ticket").data.ticket.order_id ||
190
+ !completed.get("prepare-refund").action?.preparedActionId ||
191
+ completed.get("prepare-refund").action?.approvalRequired !== true
192
+ )
193
+ throw new Error("The customer flow must read and prepare once for approval.");
194
+ const [noMatch] = await discovery.search({
195
+ tasks: [{ id: "clarify", text: "quasar" }],
196
+ runtimeContext: harness.runtimeContext
197
+ });
198
+ const deniedContext = structuredClone(harness.runtimeContext);
199
+ deniedContext.subject.permissions = [];
200
+ const [denied] = await discovery.search({
201
+ tasks: [{ id: "denied", text: "ticket" }],
202
+ runtimeContext: deniedContext
203
+ });
204
+ return {
205
+ mock: true,
206
+ customerRequest,
207
+ plannedTasks: tasks,
208
+ taskResults,
209
+ planning: {
210
+ turns: planning.client.requests.length,
211
+ usage: modelUsage(planning.usage)
212
+ },
213
+ ordinaryModel: { turns: ordinaryModelTurns, usage: modelUsage(ordinaryUsage) },
214
+ discoveryUsage: aggregateDiscoveryUsage(
215
+ taskResults.flatMap((result) => result.discoveryUsage.stages),
216
+ taskResults.reduce(
217
+ (sum, result) => sum + result.discoveryUsage.totals.elapsedMs,
218
+ 0
219
+ )
220
+ ),
221
+ dedicatedSelectorCalls: 0,
222
+ embeddingAttempts: 0,
223
+ readCalls: harness.readCalls.length,
224
+ prepareCalls: harness.prepareCalls.length,
225
+ commitCalls: harness.executedOperations.filter(
226
+ (operation) => operation === "commit"
227
+ ).length,
228
+ companionCases: {
229
+ noMatch: {
230
+ status: noMatch.status,
231
+ response: "Please clarify what you would like to do."
232
+ },
233
+ denied: {
234
+ status: denied.status,
235
+ exportedCount: discovery.routerFor(denied).route({
236
+ registry: harness.registry,
237
+ builtContext: await new DefaultContextBuilder().build({
238
+ baseContext: deniedContext,
239
+ registry: harness.registry,
240
+ userInput: { text: "ticket" }
241
+ }),
242
+ userInput: { text: "ticket" }
243
+ }).items.length
244
+ }
245
+ },
246
+ finalText:
247
+ "Ticket TCK-100 is open for order ORD-100. Its refund case is prepared and awaiting approval. No refund has been issued."
248
+ };
249
+ }
250
+
251
+ export async function runSimpleHybridExample() {
252
+ const { registry, runtimeContext } = await createCustomerHarness();
253
+ const client = new DeterministicMockEmbeddingClient();
254
+ const budget = createInMemoryDiscoveryBudget({
255
+ runId: runtimeContext.runId,
256
+ maxProviderCalls: 2,
257
+ maxInputTokens: 8000
258
+ });
259
+ // Administrative build is explicit and separate from serving.
260
+ const built = await buildCapabilityEmbeddingIndex({
261
+ corpus: buildCapabilitySearchCorpus(registry),
262
+ client,
263
+ budget: budget.ledger,
264
+ execution: {
265
+ budgetPolicy: budget.policy,
266
+ limits: {
267
+ maxInputTokens: 4000,
268
+ maxCharactersPerText: DISCOVERY_STANDARD_PROFILE.maxCharactersPerText,
269
+ maxCharactersPerCall: DISCOVERY_STANDARD_PROFILE.maxCharactersPerCall
270
+ }
271
+ },
272
+ limits: DISCOVERY_STANDARD_PROFILE.indexLimits,
273
+ batchSize: 3,
274
+ maxProviderCalls: 1,
275
+ deadlineAt: Date.now() + 30_000,
276
+ runId: runtimeContext.runId,
277
+ requestId: runtimeContext.requestId
278
+ });
279
+ if (built.status !== "built")
280
+ throw new Error("The explicit mock build must succeed.");
281
+ const options = {
282
+ registry,
283
+ mode: "hybrid",
284
+ embedding: { client, index: built.index, maxInputTokens: 4000 }
285
+ };
286
+ const [result] = await createCapabilityDiscovery(options).search({
287
+ tasks: [{ id: "case", text: "case details" }],
288
+ runtimeContext,
289
+ budget
290
+ });
291
+ const before = client.calls.length;
292
+ const [invalid] = await createCapabilityDiscovery({
293
+ ...options,
294
+ embedding: { ...options.embedding, index: {} }
295
+ }).search({
296
+ tasks: [{ id: "invalid-index", text: "ticket" }],
297
+ runtimeContext,
298
+ budget
299
+ });
300
+ if (
301
+ result.status !== "matched" ||
302
+ invalid.status !== "unavailable" ||
303
+ client.calls.length !== before
304
+ )
305
+ throw new Error("Hybrid contract example failed.");
306
+ return {
307
+ mock: true,
308
+ result,
309
+ invalidIndexStatus: invalid.status,
310
+ invalidIndexQueryCalls: client.calls.length - before,
311
+ embeddingAttempts: client.calls.length,
312
+ indexFingerprint: built.index.indexFingerprint,
313
+ usage: aggregateDiscoveryUsage(
314
+ [...built.usage.stages, ...result.usage.stages, ...invalid.usage.stages],
315
+ built.usage.totals.elapsedMs +
316
+ result.usage.totals.elapsedMs +
317
+ invalid.usage.totals.elapsedMs
318
+ )
319
+ };
320
+ }
@@ -0,0 +1,175 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import {
3
+ buildRegistry,
4
+ loadManifests,
5
+ toOpenAIResponsesToolName,
6
+ validateManifests
7
+ } from "ai-capability-framework";
8
+ import {
9
+ AicfActionLifecycleManager,
10
+ AicfHandlerRegistry,
11
+ AicfToolExecutor,
12
+ buildRuntimeContext,
13
+ DefaultContextBuilder,
14
+ DefaultPolicyBroker,
15
+ InMemoryApprovalStore,
16
+ InMemoryAuditSink,
17
+ InMemoryIdempotencyStore,
18
+ InMemoryPreparedActionStore,
19
+ StaticAuthPlatformAdapter
20
+ } from "ai-capability-framework/runtime";
21
+ import {
22
+ CapabilityDiscoverer,
23
+ RetrievalCapabilityRouter
24
+ } from "ai-capability-framework/discovery";
25
+ import {
26
+ MockOpenAIResponsesClient,
27
+ mockFunctionCallResponse,
28
+ mockTextResponse,
29
+ runOpenAIResponses
30
+ } from "ai-capability-framework/openai";
31
+ import { retrievalCases } from "./retrieval-cases.mjs";
32
+
33
+ export const discoveryConfiguration = {
34
+ mode: "lexical",
35
+ policyVersion: "example.lexical.v1",
36
+ limits: { maxCandidates: 2 }
37
+ };
38
+
39
+ export async function createDiscoveryExample() {
40
+ const loaded = await loadManifests({
41
+ path: fileURLToPath(new URL("../support/", import.meta.url))
42
+ });
43
+ if (loaded.errors.length || !validateManifests(loaded.manifests).valid) {
44
+ throw new Error("The synthetic support manifests must validate.");
45
+ }
46
+ const registry = buildRegistry(loaded.manifests);
47
+ const runtimeContext = await buildRuntimeContext({
48
+ adapter: new StaticAuthPlatformAdapter({
49
+ account: { accountId: "account_example", tenantId: "tenant_example" },
50
+ subject: {
51
+ userId: "user_example",
52
+ roles: ["support_agent"],
53
+ permissions: ["ticket.read", "refund.case.create"]
54
+ }
55
+ }),
56
+ autonomy: {
57
+ autonomyTier: "A2",
58
+ maxRiskTier: "medium",
59
+ allowSideEffects: true,
60
+ allowExternalMessages: false,
61
+ allowMoneyMovement: false,
62
+ allowPermissionChanges: false
63
+ },
64
+ environment: "test",
65
+ runId: "run_example",
66
+ requestId: "request_example"
67
+ });
68
+ const userInput = { text: "Read ticket TCK-100." };
69
+ const contextBuilder = new DefaultContextBuilder();
70
+ const builtContext = await contextBuilder.build({
71
+ baseContext: runtimeContext,
72
+ registry,
73
+ userInput
74
+ });
75
+ const handlers = new AicfHandlerRegistry({ registry });
76
+ const readCalls = [];
77
+ handlers.register({
78
+ capabilityId: "support.ticket.get",
79
+ read: ({ args }) => {
80
+ readCalls.push(args.ticket_id);
81
+ return {
82
+ ticket: { ticket_id: args.ticket_id, status: "open", order_id: "ORD-100" }
83
+ };
84
+ }
85
+ });
86
+ const policyBroker = new DefaultPolicyBroker();
87
+ const auditSink = new InMemoryAuditSink();
88
+ const lifecycle = new AicfActionLifecycleManager({
89
+ registry,
90
+ handlers,
91
+ policyBroker,
92
+ auditSink,
93
+ approvalStore: new InMemoryApprovalStore(),
94
+ idempotencyStore: new InMemoryIdempotencyStore(),
95
+ preparedActionStore: new InMemoryPreparedActionStore()
96
+ });
97
+ const executor = new AicfToolExecutor({
98
+ registry,
99
+ handlers,
100
+ policyBroker,
101
+ auditSink,
102
+ actionLifecycle: lifecycle
103
+ });
104
+ const router = new RetrievalCapabilityRouter({
105
+ mode: "native_tool_selection",
106
+ discovery: discoveryConfiguration
107
+ });
108
+ return {
109
+ registry,
110
+ runtimeContext,
111
+ builtContext,
112
+ contextBuilder,
113
+ userInput,
114
+ executor,
115
+ router,
116
+ readCalls
117
+ };
118
+ }
119
+
120
+ export async function runDiscoveryExample() {
121
+ const harness = await createDiscoveryExample();
122
+ const routeRequest = {
123
+ registry: harness.registry,
124
+ builtContext: harness.builtContext,
125
+ userInput: harness.userInput
126
+ };
127
+ const batch = await new CapabilityDiscoverer(discoveryConfiguration).discover({
128
+ routeRequest,
129
+ runId: harness.runtimeContext.runId,
130
+ requestId: harness.runtimeContext.requestId,
131
+ queries: retrievalCases.map(({ id, fragments }) => ({ id, fragments }))
132
+ });
133
+ for (const [index, result] of batch.entries()) {
134
+ const fixture = retrievalCases[index];
135
+ const ids = result.candidates.map((candidate) => candidate.capabilityId);
136
+ if (
137
+ ids.some(
138
+ (id) => !fixture.compatibleIds.includes(id) || fixture.forbiddenIds.includes(id)
139
+ ) ||
140
+ fixture.compatibleIds.some((id) => !ids.includes(id))
141
+ ) {
142
+ throw new Error("Retrieval fixture failed: " + fixture.id);
143
+ }
144
+ }
145
+ const routed = await harness.router.routeWithDiagnostics(routeRequest);
146
+ const client = new MockOpenAIResponsesClient([
147
+ mockFunctionCallResponse({
148
+ name: toOpenAIResponsesToolName("support.ticket.get"),
149
+ args: { ticket_id: "TCK-100" },
150
+ callId: "call_example"
151
+ }),
152
+ mockTextResponse("Ticket TCK-100 is open.", "response_example")
153
+ ]);
154
+ const loop = await runOpenAIResponses({
155
+ ...harness,
156
+ client,
157
+ model: "mock-model"
158
+ });
159
+ if (loop.status !== "completed" || loop.toolResults[0]?.status !== "success") {
160
+ throw new Error("The mocked ordinary model/tool loop must complete.");
161
+ }
162
+ return {
163
+ fixtureResults: batch.map(({ queryId, status, candidates }) => ({
164
+ queryId,
165
+ status,
166
+ capabilityIds: candidates.map((candidate) => candidate.capabilityId)
167
+ })),
168
+ slice: routed.slice,
169
+ exportedToolNames: client.requests[0].tools.map((tool) => tool.name),
170
+ discoveryUsage: routed.discovery[0].usage,
171
+ ordinaryMockModelTurns: client.requests.length,
172
+ toolStatus: loop.toolResults[0].status,
173
+ finalText: loop.finalText
174
+ };
175
+ }