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.
- package/CHANGELOG.md +42 -0
- package/README.md +38 -30
- package/ROADMAP.md +14 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +32 -1
- package/dist/discovery/bindings.d.ts +7 -0
- package/dist/discovery/bindings.d.ts.map +1 -0
- package/dist/discovery/bindings.js +24 -0
- package/dist/discovery/budget.d.ts +17 -0
- package/dist/discovery/budget.d.ts.map +1 -0
- package/dist/discovery/budget.js +77 -0
- package/dist/discovery/configuration.d.ts +10 -0
- package/dist/discovery/configuration.d.ts.map +1 -0
- package/dist/discovery/configuration.js +63 -0
- package/dist/discovery/corpus.d.ts +8 -0
- package/dist/discovery/corpus.d.ts.map +1 -0
- package/dist/discovery/corpus.js +64 -0
- package/dist/discovery/diagnostics.d.ts +15 -0
- package/dist/discovery/diagnostics.d.ts.map +1 -0
- package/dist/discovery/diagnostics.js +11 -0
- package/dist/discovery/discoverer.d.ts +23 -0
- package/dist/discovery/discoverer.d.ts.map +1 -0
- package/dist/discovery/discoverer.js +261 -0
- package/dist/discovery/embedding-configuration.d.ts +12 -0
- package/dist/discovery/embedding-configuration.d.ts.map +1 -0
- package/dist/discovery/embedding-configuration.js +81 -0
- package/dist/discovery/embedding-types.d.ts +75 -0
- package/dist/discovery/embedding-types.d.ts.map +1 -0
- package/dist/discovery/embedding-types.js +1 -0
- package/dist/discovery/embeddings.d.ts +38 -0
- package/dist/discovery/embeddings.d.ts.map +1 -0
- package/dist/discovery/embeddings.js +71 -0
- package/dist/discovery/evaluation-validation.d.ts +8 -0
- package/dist/discovery/evaluation-validation.d.ts.map +1 -0
- package/dist/discovery/evaluation-validation.js +19 -0
- package/dist/discovery/evaluation.d.ts +84 -0
- package/dist/discovery/evaluation.d.ts.map +1 -0
- package/dist/discovery/evaluation.js +310 -0
- package/dist/discovery/export.d.ts +14 -0
- package/dist/discovery/export.d.ts.map +1 -0
- package/dist/discovery/export.js +81 -0
- package/dist/discovery/facade.d.ts +82 -0
- package/dist/discovery/facade.d.ts.map +1 -0
- package/dist/discovery/facade.js +266 -0
- package/dist/discovery/fingerprint.d.ts +71 -0
- package/dist/discovery/fingerprint.d.ts.map +1 -0
- package/dist/discovery/fingerprint.js +67 -0
- package/dist/discovery/index-builder.d.ts +3 -0
- package/dist/discovery/index-builder.d.ts.map +1 -0
- package/dist/discovery/index-builder.js +129 -0
- package/dist/discovery/index-validation.d.ts +12 -0
- package/dist/discovery/index-validation.d.ts.map +1 -0
- package/dist/discovery/index-validation.js +117 -0
- package/dist/discovery/index.d.ts +32 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +18 -0
- package/dist/discovery/json-snapshot.d.ts +3 -0
- package/dist/discovery/json-snapshot.d.ts.map +1 -0
- package/dist/discovery/json-snapshot.js +11 -0
- package/dist/discovery/lexical.d.ts +12 -0
- package/dist/discovery/lexical.d.ts.map +1 -0
- package/dist/discovery/lexical.js +26 -0
- package/dist/discovery/metadata.d.ts +4 -0
- package/dist/discovery/metadata.d.ts.map +1 -0
- package/dist/discovery/metadata.js +33 -0
- package/dist/discovery/ranking.d.ts +7 -0
- package/dist/discovery/ranking.d.ts.map +1 -0
- package/dist/discovery/ranking.js +68 -0
- package/dist/discovery/replay.d.ts +50 -0
- package/dist/discovery/replay.d.ts.map +1 -0
- package/dist/discovery/replay.js +175 -0
- package/dist/discovery/retrieval-policy.d.ts +5 -0
- package/dist/discovery/retrieval-policy.d.ts.map +1 -0
- package/dist/discovery/retrieval-policy.js +10 -0
- package/dist/discovery/router.d.ts +39 -0
- package/dist/discovery/router.d.ts.map +1 -0
- package/dist/discovery/router.js +98 -0
- package/dist/discovery/selection-attempt.d.ts +20 -0
- package/dist/discovery/selection-attempt.d.ts.map +1 -0
- package/dist/discovery/selection-attempt.js +39 -0
- package/dist/discovery/selection-cards.d.ts +12 -0
- package/dist/discovery/selection-cards.d.ts.map +1 -0
- package/dist/discovery/selection-cards.js +79 -0
- package/dist/discovery/selection-configuration.d.ts +18 -0
- package/dist/discovery/selection-configuration.d.ts.map +1 -0
- package/dist/discovery/selection-configuration.js +71 -0
- package/dist/discovery/selection-response.d.ts +13 -0
- package/dist/discovery/selection-response.d.ts.map +1 -0
- package/dist/discovery/selection-response.js +47 -0
- package/dist/discovery/selection-types.d.ts +18 -0
- package/dist/discovery/selection-types.d.ts.map +1 -0
- package/dist/discovery/selection-types.js +1 -0
- package/dist/discovery/selection.d.ts +6 -0
- package/dist/discovery/selection.d.ts.map +1 -0
- package/dist/discovery/selection.js +145 -0
- package/dist/discovery/types.d.ts +291 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +1 -0
- package/dist/discovery/usage.d.ts +2 -0
- package/dist/discovery/usage.d.ts.map +1 -0
- package/dist/discovery/usage.js +1 -0
- package/dist/discovery/vectors.d.ts +5 -0
- package/dist/discovery/vectors.d.ts.map +1 -0
- package/dist/discovery/vectors.js +43 -0
- package/dist/discovery/versions.d.ts +37 -0
- package/dist/discovery/versions.d.ts.map +1 -0
- package/dist/discovery/versions.js +36 -0
- package/dist/evals-live/live-eval-runner.d.ts +1 -0
- package/dist/evals-live/live-eval-runner.d.ts.map +1 -1
- package/dist/evals-live/live-eval-runner.js +12 -1
- package/dist/evals-live/types.d.ts +3 -0
- package/dist/evals-live/types.d.ts.map +1 -1
- package/dist/generated/manifest-types.d.ts +3655 -0
- package/dist/generated/manifest-types.d.ts.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +8 -0
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +11 -2
- package/dist/mcp-server/types.d.ts +4 -0
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/observability/redaction.d.ts.map +1 -1
- package/dist/observability/redaction.js +6 -2
- package/dist/observability/types.d.ts +1 -1
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/openai/index.d.ts +2 -0
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +1 -0
- package/dist/openai/responses-runner.d.ts +1 -1
- package/dist/openai/responses-runner.d.ts.map +1 -1
- package/dist/openai/responses-runner.js +134 -7
- package/dist/openai/selection-client.d.ts +28 -0
- package/dist/openai/selection-client.d.ts.map +1 -0
- package/dist/openai/selection-client.js +95 -0
- package/dist/openai/tool-loop.js +1 -1
- package/dist/openai/types.d.ts +19 -2
- package/dist/openai/types.d.ts.map +1 -1
- package/dist/replay/discovery-snapshot.d.ts +10 -0
- package/dist/replay/discovery-snapshot.d.ts.map +1 -0
- package/dist/replay/discovery-snapshot.js +17 -0
- package/dist/replay/recorder.d.ts.map +1 -1
- package/dist/replay/recorder.js +4 -0
- package/dist/replay/redaction.js +5 -1
- package/dist/replay/replayer.d.ts.map +1 -1
- package/dist/replay/replayer.js +14 -1
- package/dist/replay/types.d.ts +3 -0
- package/dist/replay/types.d.ts.map +1 -1
- package/dist/replay/validation.d.ts.map +1 -1
- package/dist/replay/validation.js +6 -0
- package/dist/runtime/bounded-json.d.ts +4 -0
- package/dist/runtime/bounded-json.d.ts.map +1 -0
- package/dist/runtime/bounded-json.js +74 -0
- package/dist/runtime/eligibility.d.ts +4 -0
- package/dist/runtime/eligibility.d.ts.map +1 -0
- package/dist/runtime/eligibility.js +128 -0
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/provider-attempt.d.ts +11 -0
- package/dist/runtime/provider-attempt.d.ts.map +1 -0
- package/dist/runtime/provider-attempt.js +221 -0
- package/dist/runtime/provider-run.d.ts +10 -0
- package/dist/runtime/provider-run.d.ts.map +1 -0
- package/dist/runtime/provider-run.js +123 -0
- package/dist/runtime/provider-stop.d.ts +9 -0
- package/dist/runtime/provider-stop.d.ts.map +1 -0
- package/dist/runtime/provider-stop.js +28 -0
- package/dist/runtime/provider-types.d.ts +189 -0
- package/dist/runtime/provider-types.d.ts.map +1 -0
- package/dist/runtime/provider-types.js +1 -0
- package/dist/runtime/provider-usage.d.ts +9 -0
- package/dist/runtime/provider-usage.d.ts.map +1 -0
- package/dist/runtime/provider-usage.js +69 -0
- package/dist/runtime/router.d.ts.map +1 -1
- package/dist/runtime/router.js +3 -118
- package/dist/runtime/types.d.ts +9 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validator.d.ts +3 -0
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +32 -0
- package/docs/api/public-api-policy.md +10 -0
- package/docs/api/stable-workflow.md +58 -0
- package/docs/api.md +7 -0
- package/docs/capability-manifests.md +24 -0
- package/docs/development.md +22 -0
- package/docs/discovery.md +186 -0
- package/docs/evals/discovery-live.md +199 -0
- package/docs/evals/discovery-qualification.md +158 -0
- package/docs/evals/overview.md +9 -0
- package/docs/getting-started/installation.md +11 -12
- package/docs/getting-started/openai-quickstart.md +16 -5
- package/docs/getting-started/provider-neutral-quickstart.md +247 -88
- package/docs/getting-started/quickstart.md +8 -30
- package/docs/index.md +10 -0
- package/docs/release.md +29 -5
- package/docs/runtime.md +27 -0
- package/docs/start-here.md +23 -134
- package/examples/12-capability-discovery-selection/README.md +346 -0
- package/examples/12-capability-discovery-selection/customer-cases.mjs +52 -0
- package/examples/12-capability-discovery-selection/customer-harness.mjs +91 -0
- package/examples/12-capability-discovery-selection/customer-request-example.mjs +320 -0
- package/examples/12-capability-discovery-selection/discovery-example.mjs +175 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.suite.json +7887 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v2.suite.json +8167 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v3.suite.json +8342 -0
- package/examples/12-capability-discovery-selection/evaluation/application.mjs +305 -0
- package/examples/12-capability-discovery-selection/evaluation/campaign.mjs +248 -0
- package/examples/12-capability-discovery-selection/evaluation/clients.mjs +433 -0
- package/examples/12-capability-discovery-selection/evaluation/continuation.mjs +208 -0
- package/examples/12-capability-discovery-selection/evaluation/durable-budget.mjs +420 -0
- package/examples/12-capability-discovery-selection/evaluation/freeze.mjs +9 -0
- package/examples/12-capability-discovery-selection/evaluation/harness.mjs +618 -0
- package/examples/12-capability-discovery-selection/evaluation/mock-host.mjs +260 -0
- package/examples/12-capability-discovery-selection/evaluation/preflight.mjs +115 -0
- package/examples/12-capability-discovery-selection/evaluation/qualification-workflows.mjs +87 -0
- package/examples/12-capability-discovery-selection/evaluation/regrade.mjs +281 -0
- package/examples/12-capability-discovery-selection/evaluation/report.mjs +517 -0
- package/examples/12-capability-discovery-selection/evaluation/runner.mjs +613 -0
- package/examples/12-capability-discovery-selection/evaluation/saved-campaign.mjs +385 -0
- package/examples/12-capability-discovery-selection/evaluation/wording.mjs +115 -0
- package/examples/12-capability-discovery-selection/evaluation/workflow-application.mjs +378 -0
- package/examples/12-capability-discovery-selection/evaluation/workflow-cases.mjs +90 -0
- package/examples/12-capability-discovery-selection/evaluation-example.mjs +154 -0
- package/examples/12-capability-discovery-selection/hybrid-example.mjs +174 -0
- package/examples/12-capability-discovery-selection/mock-embeddings.mjs +101 -0
- package/examples/12-capability-discovery-selection/mock-selection.mjs +78 -0
- package/examples/12-capability-discovery-selection/pipeline-example.mjs +496 -0
- package/examples/12-capability-discovery-selection/retrieval-cases.mjs +31 -0
- package/examples/12-capability-discovery-selection/run-campaign-mock.mjs +40 -0
- package/examples/12-capability-discovery-selection/run-customer-mock.mjs +15 -0
- package/examples/12-capability-discovery-selection/run-evaluation-live.mjs +10 -0
- package/examples/12-capability-discovery-selection/run-evaluation-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-evaluation-regrade.mjs +71 -0
- package/examples/12-capability-discovery-selection/run-hybrid-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-pipeline-mock.mjs +2 -0
- package/examples/12-capability-discovery-selection/run-selector-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/selection-example.mjs +88 -0
- package/package.json +17 -4
- package/schemas/discovery/embedding-index.schema.json +71 -0
- package/schemas/discovery/eval-observations.schema.json +409 -0
- package/schemas/discovery/eval-suite.schema.json +375 -0
- package/schemas/discovery/metadata.schema.json +30 -0
- package/schemas/discovery/selection-response.schema.json +47 -0
- package/schemas/replay/discovery-snapshot.schema.json +335 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { createOpenAISelectionClient } from "ai-capability-framework/openai";
|
|
2
|
+
import { hashDiscoveryContent } from "ai-capability-framework/discovery";
|
|
3
|
+
import { EXECUTION } from "./campaign.mjs";
|
|
4
|
+
|
|
5
|
+
const inputItems = (value) =>
|
|
6
|
+
typeof value === "string"
|
|
7
|
+
? [{ role: "user", content: value }]
|
|
8
|
+
: structuredClone(value ?? []);
|
|
9
|
+
|
|
10
|
+
function usableOutput(response) {
|
|
11
|
+
if (!Array.isArray(response?.output) || response.output.length > 64) return false;
|
|
12
|
+
let actionable = false;
|
|
13
|
+
for (const item of response.output) {
|
|
14
|
+
if (item?.type === "function_call") {
|
|
15
|
+
if (
|
|
16
|
+
typeof item.name !== "string" ||
|
|
17
|
+
typeof item.call_id !== "string" ||
|
|
18
|
+
typeof item.arguments !== "string" ||
|
|
19
|
+
item.arguments.length > 128_000
|
|
20
|
+
)
|
|
21
|
+
return false;
|
|
22
|
+
actionable = true;
|
|
23
|
+
} else if (item?.type === "message") {
|
|
24
|
+
if (!Array.isArray(item.content) || item.content.length > 64) return false;
|
|
25
|
+
for (const content of item.content) {
|
|
26
|
+
if (
|
|
27
|
+
content?.type !== "output_text" ||
|
|
28
|
+
typeof content.text !== "string" ||
|
|
29
|
+
content.text.length > 128_000
|
|
30
|
+
)
|
|
31
|
+
return false;
|
|
32
|
+
actionable ||= !!content.text.trim();
|
|
33
|
+
}
|
|
34
|
+
} else if (item?.type !== "reasoning") return false;
|
|
35
|
+
}
|
|
36
|
+
return actionable;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function decorateRequest(request, history = []) {
|
|
40
|
+
const decorated = {
|
|
41
|
+
...structuredClone(request),
|
|
42
|
+
reasoning: { effort: EXECUTION.reasoning },
|
|
43
|
+
service_tier: "default",
|
|
44
|
+
parallel_tool_calls: false,
|
|
45
|
+
truncation: "disabled",
|
|
46
|
+
stream: false,
|
|
47
|
+
store: false
|
|
48
|
+
};
|
|
49
|
+
// Replay complete prior inputs/outputs explicitly; no server-side stored conversation is required.
|
|
50
|
+
if (request.previous_response_id) {
|
|
51
|
+
if (!history.length || history.at(-1).response.id !== request.previous_response_id)
|
|
52
|
+
throw new Error("Missing continuation fixtures.");
|
|
53
|
+
decorated.input = [
|
|
54
|
+
...history.flatMap((turn) => [
|
|
55
|
+
...inputItems(turn.request.input),
|
|
56
|
+
...structuredClone(turn.response.output ?? [])
|
|
57
|
+
]),
|
|
58
|
+
...inputItems(request.input)
|
|
59
|
+
];
|
|
60
|
+
delete decorated.previous_response_id;
|
|
61
|
+
}
|
|
62
|
+
delete decorated.temperature;
|
|
63
|
+
return decorated;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** No credentials or SDK are discovered here. The host supplies both client and embedding tokenizer. */
|
|
67
|
+
export function createEvaluationClients({
|
|
68
|
+
sdk,
|
|
69
|
+
ledger,
|
|
70
|
+
mock,
|
|
71
|
+
tokenizer,
|
|
72
|
+
tokenizerIdentity,
|
|
73
|
+
pricing,
|
|
74
|
+
scope = {}
|
|
75
|
+
}) {
|
|
76
|
+
if (
|
|
77
|
+
typeof mock !== "boolean" ||
|
|
78
|
+
typeof tokenizer !== "function" ||
|
|
79
|
+
typeof tokenizerIdentity !== "string" ||
|
|
80
|
+
!tokenizerIdentity.trim() ||
|
|
81
|
+
typeof sdk?.responses?.create !== "function" ||
|
|
82
|
+
typeof sdk?.responses?.inputTokens?.count !== "function" ||
|
|
83
|
+
typeof sdk?.embeddings?.create !== "function"
|
|
84
|
+
)
|
|
85
|
+
throw new Error("Explicit clients and token preflights are required.");
|
|
86
|
+
if (
|
|
87
|
+
!pricing ||
|
|
88
|
+
Object.keys(pricing).some(
|
|
89
|
+
(key) =>
|
|
90
|
+
![
|
|
91
|
+
"inputPerMillion",
|
|
92
|
+
"outputPerMillion",
|
|
93
|
+
"embeddingPerMillion",
|
|
94
|
+
"tokenCountPerRequest",
|
|
95
|
+
"premiumMultiplier",
|
|
96
|
+
"checkedAt"
|
|
97
|
+
].includes(key)
|
|
98
|
+
)
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Pricing accepts only explicit numeric rates and its verification timestamp."
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
for (const key of [
|
|
105
|
+
"inputPerMillion",
|
|
106
|
+
"outputPerMillion",
|
|
107
|
+
"embeddingPerMillion",
|
|
108
|
+
"tokenCountPerRequest",
|
|
109
|
+
"premiumMultiplier"
|
|
110
|
+
]) {
|
|
111
|
+
if (
|
|
112
|
+
typeof pricing?.[key] !== "number" ||
|
|
113
|
+
!Number.isFinite(pricing[key]) ||
|
|
114
|
+
pricing[key] < 0
|
|
115
|
+
)
|
|
116
|
+
throw new Error("Explicit finite pricing is required.");
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
pricing.premiumMultiplier < 1 ||
|
|
120
|
+
(!mock &&
|
|
121
|
+
(!pricing.checkedAt ||
|
|
122
|
+
Date.now() - Date.parse(pricing.checkedAt) > 24 * 60 * 60_000 ||
|
|
123
|
+
!Number.isFinite(Date.parse(pricing.checkedAt)) ||
|
|
124
|
+
Date.parse(pricing.checkedAt) > Date.now() + 300_000))
|
|
125
|
+
)
|
|
126
|
+
throw new Error("Live pricing must have been checked in the last 24 hours.");
|
|
127
|
+
// Campaign-specific conservative floors, verified against official Standard pricing on 2026-10-16.
|
|
128
|
+
// They are not framework pricing defaults. Hosts must raise them for higher current rates/premiums.
|
|
129
|
+
if (
|
|
130
|
+
!mock &&
|
|
131
|
+
(pricing.inputPerMillion < 0.125 ||
|
|
132
|
+
pricing.outputPerMillion < 0.5 ||
|
|
133
|
+
pricing.embeddingPerMillion < 0.02)
|
|
134
|
+
) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"Live pricing is below the campaign's verified conservative floor."
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const pricingRates = Object.fromEntries(
|
|
140
|
+
Object.entries(pricing).filter(([key]) => key !== "checkedAt")
|
|
141
|
+
);
|
|
142
|
+
const identity = hashDiscoveryContent({
|
|
143
|
+
execution: EXECUTION,
|
|
144
|
+
tokenizerIdentity,
|
|
145
|
+
pricing: pricingRates,
|
|
146
|
+
mock
|
|
147
|
+
});
|
|
148
|
+
const configuration = Object.freeze({
|
|
149
|
+
provider: "openai",
|
|
150
|
+
model: EXECUTION.model,
|
|
151
|
+
configurationId: `evaluation.responses.${identity.slice(7, 23)}`
|
|
152
|
+
});
|
|
153
|
+
const embeddingConfiguration = Object.freeze({
|
|
154
|
+
provider: "openai",
|
|
155
|
+
model: EXECUTION.embeddingModel,
|
|
156
|
+
configurationId: `evaluation.embedding.${identity.slice(7, 23)}`,
|
|
157
|
+
dimensions: EXECUTION.dimensions
|
|
158
|
+
});
|
|
159
|
+
const prepared = new WeakMap();
|
|
160
|
+
const check = () => {
|
|
161
|
+
if (
|
|
162
|
+
scope.signal?.aborted ||
|
|
163
|
+
Date.now() >= (scope.deadlineAt ?? Infinity) ||
|
|
164
|
+
scope.guard?.()
|
|
165
|
+
)
|
|
166
|
+
throw Object.assign(new Error("Evaluation binding is stale or cancelled."), {
|
|
167
|
+
providerAttempts: 0
|
|
168
|
+
});
|
|
169
|
+
const control = scope.controls?.evaluate({
|
|
170
|
+
operation: "provider_call",
|
|
171
|
+
providerId: "openai",
|
|
172
|
+
model: EXECUTION.model,
|
|
173
|
+
registry: scope.registry,
|
|
174
|
+
runtimeContext: scope.runtimeContext,
|
|
175
|
+
usage: {
|
|
176
|
+
providerCalls: ledger.snapshot().httpRequests,
|
|
177
|
+
retries: 0,
|
|
178
|
+
runtimeMs: 0,
|
|
179
|
+
toolCalls: 0
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
if (control?.status === "denied")
|
|
183
|
+
throw Object.assign(new Error("Evaluation provider control denied dispatch."), {
|
|
184
|
+
providerAttempts: 0
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
const estimateCost = (usage, config) => {
|
|
188
|
+
if (
|
|
189
|
+
usage.inputTokens === undefined ||
|
|
190
|
+
(config.model !== EXECUTION.embeddingModel && usage.outputTokens === undefined)
|
|
191
|
+
)
|
|
192
|
+
return undefined;
|
|
193
|
+
const inputRate =
|
|
194
|
+
config.model === EXECUTION.embeddingModel
|
|
195
|
+
? pricing.embeddingPerMillion
|
|
196
|
+
: pricing.inputPerMillion;
|
|
197
|
+
return (
|
|
198
|
+
((usage.inputTokens * inputRate +
|
|
199
|
+
(config.model === EXECUTION.embeddingModel
|
|
200
|
+
? 0
|
|
201
|
+
: usage.outputTokens * pricing.outputPerMillion)) /
|
|
202
|
+
1_000_000) *
|
|
203
|
+
pricing.premiumMultiplier
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
const maxCost = (input = 8000, output = 1000, embedding = false) =>
|
|
207
|
+
estimateCost(
|
|
208
|
+
{ inputTokens: input, outputTokens: output },
|
|
209
|
+
embedding ? embeddingConfiguration : configuration
|
|
210
|
+
);
|
|
211
|
+
const inputTokenPreflight = async ({
|
|
212
|
+
request,
|
|
213
|
+
history,
|
|
214
|
+
maxInputTokens,
|
|
215
|
+
signal,
|
|
216
|
+
deadlineAt
|
|
217
|
+
}) => {
|
|
218
|
+
check();
|
|
219
|
+
const value = decorateRequest(request, history);
|
|
220
|
+
const allowed = [
|
|
221
|
+
"model",
|
|
222
|
+
"input",
|
|
223
|
+
"instructions",
|
|
224
|
+
"parallel_tool_calls",
|
|
225
|
+
"reasoning",
|
|
226
|
+
"text",
|
|
227
|
+
"tool_choice",
|
|
228
|
+
"tools",
|
|
229
|
+
"truncation"
|
|
230
|
+
];
|
|
231
|
+
const payload = Object.fromEntries(
|
|
232
|
+
allowed.filter((key) => value[key] !== undefined).map((key) => [key, value[key]])
|
|
233
|
+
);
|
|
234
|
+
const response = await ledger
|
|
235
|
+
.transport(
|
|
236
|
+
"token_count",
|
|
237
|
+
() =>
|
|
238
|
+
sdk.responses.inputTokens.count(payload, {
|
|
239
|
+
signal,
|
|
240
|
+
timeout: Math.max(1, deadlineAt - Date.now()),
|
|
241
|
+
maxRetries: 0
|
|
242
|
+
}),
|
|
243
|
+
{ signal, deadlineAt, fee: pricing.tokenCountPerRequest }
|
|
244
|
+
)
|
|
245
|
+
.catch(() => {
|
|
246
|
+
throw Object.assign(new Error("Input token preflight unavailable."), {
|
|
247
|
+
providerAttempts: 0,
|
|
248
|
+
completedResponses: 0,
|
|
249
|
+
usage: {}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
if (
|
|
253
|
+
!Number.isSafeInteger(response?.input_tokens) ||
|
|
254
|
+
response.input_tokens < 0 ||
|
|
255
|
+
response.input_tokens > maxInputTokens
|
|
256
|
+
)
|
|
257
|
+
throw Object.assign(new Error("Complete input exceeds its token ceiling."), {
|
|
258
|
+
providerAttempts: 0
|
|
259
|
+
});
|
|
260
|
+
check();
|
|
261
|
+
prepared.set(request, value);
|
|
262
|
+
return response.input_tokens;
|
|
263
|
+
};
|
|
264
|
+
const client = {
|
|
265
|
+
responses: {
|
|
266
|
+
async create(request, options = {}) {
|
|
267
|
+
const value = prepared.get(request);
|
|
268
|
+
if (!value)
|
|
269
|
+
throw Object.assign(
|
|
270
|
+
new Error("A complete input preflight must precede generation."),
|
|
271
|
+
{ providerAttempts: 0 }
|
|
272
|
+
);
|
|
273
|
+
prepared.delete(request);
|
|
274
|
+
check();
|
|
275
|
+
const response = await ledger.transport(
|
|
276
|
+
"generation",
|
|
277
|
+
() =>
|
|
278
|
+
sdk.responses.create(value, {
|
|
279
|
+
...options,
|
|
280
|
+
maxRetries: 0,
|
|
281
|
+
timeout: Math.max(
|
|
282
|
+
1,
|
|
283
|
+
Math.min(
|
|
284
|
+
options.timeout ?? 30_000,
|
|
285
|
+
(scope.deadlineAt ?? Date.now() + 30_000) - Date.now()
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
}),
|
|
289
|
+
{
|
|
290
|
+
signal: options.signal,
|
|
291
|
+
deadlineAt: Math.min(
|
|
292
|
+
scope.deadlineAt ?? Infinity,
|
|
293
|
+
Date.now() + (options.timeout ?? 30_000)
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
if (
|
|
298
|
+
response?.status !== "completed" ||
|
|
299
|
+
typeof response.model !== "string" ||
|
|
300
|
+
(response.model !== EXECUTION.model &&
|
|
301
|
+
!response.model.startsWith(EXECUTION.model + "-")) ||
|
|
302
|
+
!usableOutput(response) ||
|
|
303
|
+
response.output.some((item) =>
|
|
304
|
+
item?.content?.some?.((content) => content?.type === "refusal")
|
|
305
|
+
)
|
|
306
|
+
) {
|
|
307
|
+
throw Object.assign(
|
|
308
|
+
new Error("Evaluation generation response is incompatible or refused."),
|
|
309
|
+
{
|
|
310
|
+
usage: Object.fromEntries(
|
|
311
|
+
Object.entries({
|
|
312
|
+
inputTokens: response?.usage?.input_tokens,
|
|
313
|
+
outputTokens: response?.usage?.output_tokens,
|
|
314
|
+
totalTokens: response?.usage?.total_tokens,
|
|
315
|
+
cachedInputTokens:
|
|
316
|
+
response?.usage?.input_tokens_details?.cached_tokens,
|
|
317
|
+
reasoningTokens:
|
|
318
|
+
response?.usage?.output_tokens_details?.reasoning_tokens
|
|
319
|
+
}).filter(([, number]) => Number.isSafeInteger(number) && number >= 0)
|
|
320
|
+
),
|
|
321
|
+
providerAttempts: 1,
|
|
322
|
+
completedResponses: 1
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return response;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
const embeddingClient = Object.freeze({
|
|
331
|
+
mock,
|
|
332
|
+
configuration: embeddingConfiguration,
|
|
333
|
+
async embed(input) {
|
|
334
|
+
check();
|
|
335
|
+
const counts = input.texts.map((text) => tokenizer(text));
|
|
336
|
+
if (
|
|
337
|
+
counts.some((count) => !Number.isSafeInteger(count) || count < 1) ||
|
|
338
|
+
counts.reduce((a, b) => a + b, 0) > input.maxInputTokens
|
|
339
|
+
)
|
|
340
|
+
throw Object.assign(new Error("Embedding input exceeds its token ceiling."), {
|
|
341
|
+
providerAttempts: 0
|
|
342
|
+
});
|
|
343
|
+
input.beforeProviderCall?.();
|
|
344
|
+
const response = await ledger.transport(
|
|
345
|
+
"embedding",
|
|
346
|
+
() =>
|
|
347
|
+
sdk.embeddings.create(
|
|
348
|
+
{
|
|
349
|
+
model: embeddingConfiguration.model,
|
|
350
|
+
input: input.texts,
|
|
351
|
+
dimensions: EXECUTION.dimensions,
|
|
352
|
+
encoding_format: "float"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
signal: input.signal,
|
|
356
|
+
timeout: Math.max(1, input.deadlineAt - Date.now()),
|
|
357
|
+
maxRetries: 0
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
{ signal: input.signal, deadlineAt: input.deadlineAt }
|
|
361
|
+
);
|
|
362
|
+
const usage = {};
|
|
363
|
+
if (
|
|
364
|
+
Number.isSafeInteger(response?.usage?.prompt_tokens) &&
|
|
365
|
+
response.usage.prompt_tokens >= 0
|
|
366
|
+
)
|
|
367
|
+
usage.inputTokens = response.usage.prompt_tokens;
|
|
368
|
+
if (
|
|
369
|
+
Number.isSafeInteger(response?.usage?.total_tokens) &&
|
|
370
|
+
response.usage.total_tokens >= 0
|
|
371
|
+
)
|
|
372
|
+
usage.totalTokens = response.usage.total_tokens;
|
|
373
|
+
// These text-generation categories are inapplicable to this embedding operation.
|
|
374
|
+
Object.assign(usage, {
|
|
375
|
+
outputTokens: 0,
|
|
376
|
+
cachedInputTokens: 0,
|
|
377
|
+
reasoningTokens: 0
|
|
378
|
+
});
|
|
379
|
+
const cost = estimateCost(usage, embeddingConfiguration);
|
|
380
|
+
if (cost !== undefined) usage.estimatedCost = cost;
|
|
381
|
+
if (
|
|
382
|
+
response?.model !== EXECUTION.embeddingModel ||
|
|
383
|
+
!Array.isArray(response.data) ||
|
|
384
|
+
response.data.length !== input.texts.length ||
|
|
385
|
+
response.data.some(
|
|
386
|
+
(entry, index) => entry.index !== index || !Array.isArray(entry.embedding)
|
|
387
|
+
)
|
|
388
|
+
) {
|
|
389
|
+
throw Object.assign(new Error("Embedding response is incompatible."), {
|
|
390
|
+
usage,
|
|
391
|
+
providerAttempts: 1,
|
|
392
|
+
completedResponses: 1
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
vectors: response.data.map((entry) => entry.embedding),
|
|
397
|
+
configuration: embeddingConfiguration,
|
|
398
|
+
vectorDimension: EXECUTION.dimensions,
|
|
399
|
+
providerAttempts: 1,
|
|
400
|
+
usage
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
mock,
|
|
406
|
+
configuration,
|
|
407
|
+
embeddingConfiguration,
|
|
408
|
+
client,
|
|
409
|
+
embeddingClient,
|
|
410
|
+
inputTokenPreflight,
|
|
411
|
+
selectionClient: createOpenAISelectionClient({
|
|
412
|
+
client,
|
|
413
|
+
configuration,
|
|
414
|
+
mock,
|
|
415
|
+
inputTokenPreflight
|
|
416
|
+
}),
|
|
417
|
+
estimateCost,
|
|
418
|
+
maxCost,
|
|
419
|
+
tokenizerIdentity,
|
|
420
|
+
identity,
|
|
421
|
+
forScope(next) {
|
|
422
|
+
return createEvaluationClients({
|
|
423
|
+
sdk,
|
|
424
|
+
ledger,
|
|
425
|
+
mock,
|
|
426
|
+
tokenizer,
|
|
427
|
+
tokenizerIdentity,
|
|
428
|
+
pricing,
|
|
429
|
+
scope: next
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
fsyncSync,
|
|
6
|
+
openSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { hashDiscoveryContent } from "ai-capability-framework/discovery";
|
|
13
|
+
import { CAPS } from "./campaign.mjs";
|
|
14
|
+
import { openCampaignLedger } from "./durable-budget.mjs";
|
|
15
|
+
|
|
16
|
+
/** Read-only reconciliation; the caller pins both identity and exact journal bytes. */
|
|
17
|
+
export function verifyPredecessor(predecessor) {
|
|
18
|
+
if (
|
|
19
|
+
!predecessor ||
|
|
20
|
+
Object.keys(predecessor).sort().join() !== "directory,fingerprint,journalHash"
|
|
21
|
+
)
|
|
22
|
+
throw new Error("A pinned predecessor is required.");
|
|
23
|
+
const directory = path.resolve(predecessor.directory),
|
|
24
|
+
filename = path.join(directory, "journal.jsonl");
|
|
25
|
+
if (existsSync(path.join(directory, "campaign.lock")))
|
|
26
|
+
throw new Error("Predecessor is still owned.");
|
|
27
|
+
if (statSync(filename).size > 64 * 1024 * 1024)
|
|
28
|
+
throw new Error("Predecessor journal exceeds bound.");
|
|
29
|
+
const bytes = readFileSync(filename);
|
|
30
|
+
const journalHash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
31
|
+
if (journalHash !== predecessor.journalHash || !bytes.toString().endsWith("\n"))
|
|
32
|
+
throw new Error("Predecessor journal hash mismatch.");
|
|
33
|
+
let sequence = 0,
|
|
34
|
+
previous = null,
|
|
35
|
+
head,
|
|
36
|
+
halted = false;
|
|
37
|
+
const reservations = new Map(),
|
|
38
|
+
pendingHttp = new Set(),
|
|
39
|
+
pendingTrials = new Set(),
|
|
40
|
+
settled = new Set();
|
|
41
|
+
const consumed = Object.fromEntries(Object.keys(CAPS).map((key) => [key, 0]));
|
|
42
|
+
for (const line of bytes.toString().trimEnd().split("\n")) {
|
|
43
|
+
if (line.length > 128_000) throw new Error("Oversized predecessor record.");
|
|
44
|
+
const { hash, ...record } = JSON.parse(line),
|
|
45
|
+
d = record.data;
|
|
46
|
+
if (
|
|
47
|
+
record.sequence !== ++sequence ||
|
|
48
|
+
record.previous !== previous ||
|
|
49
|
+
hashDiscoveryContent(record) !== hash
|
|
50
|
+
)
|
|
51
|
+
throw new Error("Predecessor integrity failure.");
|
|
52
|
+
previous = hash;
|
|
53
|
+
if (sequence === 1) {
|
|
54
|
+
if (
|
|
55
|
+
record.type !== "campaign" ||
|
|
56
|
+
d.fingerprint !== predecessor.fingerprint ||
|
|
57
|
+
hashDiscoveryContent({ binding: d.binding, caps: d.caps }) !== d.fingerprint ||
|
|
58
|
+
hashDiscoveryContent(d.caps) !== hashDiscoveryContent(CAPS)
|
|
59
|
+
)
|
|
60
|
+
throw new Error("Predecessor campaign mismatch.");
|
|
61
|
+
head = d;
|
|
62
|
+
}
|
|
63
|
+
if (record.type === "reservation") {
|
|
64
|
+
if (reservations.has(d.id) || settled.has(d.id))
|
|
65
|
+
throw new Error("Duplicate predecessor reservation.");
|
|
66
|
+
reservations.set(d.id, d);
|
|
67
|
+
}
|
|
68
|
+
if (record.type === "settlement") {
|
|
69
|
+
const reservation = reservations.get(d.id);
|
|
70
|
+
if (!reservation || settled.has(d.id))
|
|
71
|
+
throw new Error("Duplicate predecessor settlement.");
|
|
72
|
+
const attempts = Math.max(d.providerAttempts, d.completedResponses);
|
|
73
|
+
if (!Number.isSafeInteger(attempts) || attempts < 0 || attempts > 1)
|
|
74
|
+
throw new Error("Invalid predecessor attempts.");
|
|
75
|
+
for (const key of [
|
|
76
|
+
"providerCalls",
|
|
77
|
+
"inputTokens",
|
|
78
|
+
"outputTokens",
|
|
79
|
+
"estimatedCost"
|
|
80
|
+
]) {
|
|
81
|
+
const expected =
|
|
82
|
+
key === "providerCalls"
|
|
83
|
+
? attempts
|
|
84
|
+
: (d.usage[key] ?? (attempts ? reservation.amounts[key] : 0));
|
|
85
|
+
if (!Number.isFinite(expected) || expected < 0 || d.charged[key] !== expected)
|
|
86
|
+
throw new Error("Predecessor charge mismatch.");
|
|
87
|
+
consumed[key] += expected;
|
|
88
|
+
}
|
|
89
|
+
reservations.delete(d.id);
|
|
90
|
+
settled.add(d.id);
|
|
91
|
+
}
|
|
92
|
+
if (record.type === "http.start") {
|
|
93
|
+
if (pendingHttp.has(d.id) || !Number.isFinite(d.fee) || d.fee < 0)
|
|
94
|
+
throw new Error("Invalid predecessor HTTP charge.");
|
|
95
|
+
consumed.httpRequests++;
|
|
96
|
+
consumed.estimatedCost += d.fee;
|
|
97
|
+
pendingHttp.add(d.id);
|
|
98
|
+
}
|
|
99
|
+
if (record.type === "http.end") {
|
|
100
|
+
if (!pendingHttp.delete(d.id))
|
|
101
|
+
throw new Error("Invalid predecessor HTTP completion.");
|
|
102
|
+
}
|
|
103
|
+
if (record.type === "trial.start") pendingTrials.add(d.key);
|
|
104
|
+
if (record.type === "trial.end") {
|
|
105
|
+
if (!pendingTrials.delete(d.key))
|
|
106
|
+
throw new Error("Invalid predecessor trial completion.");
|
|
107
|
+
}
|
|
108
|
+
if (record.type === "halt") halted = true;
|
|
109
|
+
}
|
|
110
|
+
if (!head || halted || reservations.size || pendingHttp.size || pendingTrials.size)
|
|
111
|
+
throw new Error("Predecessor requires reconciliation.");
|
|
112
|
+
const remaining = Object.fromEntries(
|
|
113
|
+
Object.keys(CAPS).map((key) => [
|
|
114
|
+
key,
|
|
115
|
+
key === "estimatedCost"
|
|
116
|
+
? Math.floor((CAPS[key] - consumed[key]) * 1e12) / 1e12
|
|
117
|
+
: CAPS[key] - consumed[key]
|
|
118
|
+
])
|
|
119
|
+
);
|
|
120
|
+
if (Object.values(remaining).some((value) => value < 0))
|
|
121
|
+
throw new Error("Predecessor exhausted campaign caps.");
|
|
122
|
+
return {
|
|
123
|
+
fingerprint: predecessor.fingerprint,
|
|
124
|
+
journalHash,
|
|
125
|
+
lastRecordHash: previous,
|
|
126
|
+
directory,
|
|
127
|
+
consumed,
|
|
128
|
+
remaining
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function openContinuationLedger({ predecessor, directory, binding, caps }) {
|
|
133
|
+
const prior = verifyPredecessor(predecessor),
|
|
134
|
+
resolvedCaps = { ...prior.remaining, ...caps };
|
|
135
|
+
if (
|
|
136
|
+
Object.entries(resolvedCaps).some(
|
|
137
|
+
([key, value]) =>
|
|
138
|
+
!(key in CAPS) ||
|
|
139
|
+
!Number.isFinite(value) ||
|
|
140
|
+
value < 0 ||
|
|
141
|
+
value > prior.remaining[key]
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
throw new Error("Continuation cannot increase remaining caps.");
|
|
145
|
+
const continuationBinding = {
|
|
146
|
+
...binding,
|
|
147
|
+
predecessor: {
|
|
148
|
+
fingerprint: prior.fingerprint,
|
|
149
|
+
journalHash: prior.journalHash,
|
|
150
|
+
lastRecordHash: prior.lastRecordHash
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const fingerprint = hashDiscoveryContent({
|
|
154
|
+
binding: continuationBinding,
|
|
155
|
+
caps: resolvedCaps
|
|
156
|
+
});
|
|
157
|
+
const claim = {
|
|
158
|
+
fingerprint,
|
|
159
|
+
directory: path.resolve(directory),
|
|
160
|
+
predecessorJournalHash: prior.journalHash
|
|
161
|
+
};
|
|
162
|
+
const claimPath = path.join(prior.directory, "continuation.json");
|
|
163
|
+
// Permanent exclusive ownership: a crash may resume this identity, never start a sibling.
|
|
164
|
+
try {
|
|
165
|
+
const fd = openSync(claimPath, "wx");
|
|
166
|
+
try {
|
|
167
|
+
writeFileSync(fd, JSON.stringify(claim));
|
|
168
|
+
fsyncSync(fd);
|
|
169
|
+
} finally {
|
|
170
|
+
closeSync(fd);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error.code !== "EEXIST") throw error;
|
|
174
|
+
if (
|
|
175
|
+
statSync(claimPath).size > 4096 ||
|
|
176
|
+
hashDiscoveryContent(JSON.parse(readFileSync(claimPath, "utf8"))) !==
|
|
177
|
+
hashDiscoveryContent(claim)
|
|
178
|
+
)
|
|
179
|
+
throw new Error("Predecessor already has a different continuation.");
|
|
180
|
+
}
|
|
181
|
+
if (existsSync(path.join(prior.directory, "campaign.lock")))
|
|
182
|
+
throw new Error("Predecessor became active; reconcile ownership before resuming.");
|
|
183
|
+
const ledger = openCampaignLedger({
|
|
184
|
+
directory,
|
|
185
|
+
binding: continuationBinding,
|
|
186
|
+
caps: resolvedCaps
|
|
187
|
+
});
|
|
188
|
+
return Object.freeze({
|
|
189
|
+
...ledger,
|
|
190
|
+
snapshot() {
|
|
191
|
+
const current = ledger.snapshot();
|
|
192
|
+
const cumulative = Object.fromEntries(
|
|
193
|
+
Object.keys(CAPS).map((key) => [
|
|
194
|
+
key,
|
|
195
|
+
prior.consumed[key] +
|
|
196
|
+
(key === "httpRequests" ? current.httpRequests : current.charged[key])
|
|
197
|
+
])
|
|
198
|
+
);
|
|
199
|
+
return structuredClone({
|
|
200
|
+
...current,
|
|
201
|
+
caps: resolvedCaps,
|
|
202
|
+
prior: prior.consumed,
|
|
203
|
+
cumulative,
|
|
204
|
+
predecessor: { fingerprint: prior.fingerprint, journalHash: prior.journalHash }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|