@sanity/ailf 4.5.0 → 4.6.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/dist/_vendor/ailf-core/artifact-registry.d.ts +138 -1
- package/dist/_vendor/ailf-core/artifact-registry.js +137 -4
- package/dist/_vendor/ailf-core/ports/context.d.ts +18 -0
- package/dist/_vendor/ailf-core/ports/index.d.ts +2 -0
- package/dist/_vendor/ailf-core/ports/index.js +1 -0
- package/dist/_vendor/ailf-core/ports/llm-client.d.ts +112 -0
- package/dist/_vendor/ailf-core/ports/llm-client.js +68 -0
- package/dist/_vendor/ailf-core/types/confidence.d.ts +68 -0
- package/dist/_vendor/ailf-core/types/confidence.js +49 -0
- package/dist/_vendor/ailf-core/types/index.d.ts +2 -0
- package/dist/_vendor/ailf-core/types/index.js +1 -0
- package/dist/adapters/llm/anthropic-llm-client.d.ts +48 -0
- package/dist/adapters/llm/anthropic-llm-client.js +205 -0
- package/dist/adapters/llm/fake-llm-client.d.ts +49 -0
- package/dist/adapters/llm/fake-llm-client.js +63 -0
- package/dist/adapters/llm/index.d.ts +9 -0
- package/dist/adapters/llm/index.js +4 -0
- package/dist/adapters/llm/openai-llm-client.d.ts +44 -0
- package/dist/adapters/llm/openai-llm-client.js +168 -0
- package/dist/adapters/llm/pricing.d.ts +12 -0
- package/dist/adapters/llm/pricing.js +8 -0
- package/dist/adapters/llm/retry.d.ts +56 -0
- package/dist/adapters/llm/retry.js +66 -0
- package/dist/adapters/task-sources/repo-schemas.d.ts +11 -11
- package/dist/artifact-capture/api-gateway-artifact-writer.js +2 -1
- package/dist/artifact-capture/batching-api-gateway-artifact-writer.js +2 -1
- package/dist/artifact-capture/gcs-artifact-writer.js +3 -1
- package/dist/artifact-capture/local-fs-artifact-writer.js +3 -1
- package/dist/composition-root.d.ts +23 -1
- package/dist/composition-root.js +47 -0
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared retry helper for LLMClient adapters.
|
|
3
|
+
*
|
|
4
|
+
* Bounded exponential backoff with optional `Retry-After` honoring and
|
|
5
|
+
* symmetric jitter. Treats 429 / 5xx as retryable and any other HTTP error
|
|
6
|
+
* as terminal.
|
|
7
|
+
*
|
|
8
|
+
* Errors carry the full response body on the instance for callers that need
|
|
9
|
+
* to inspect it; the message is intentionally short and body-free so it's
|
|
10
|
+
* safe to include in user-facing logs and stack traces.
|
|
11
|
+
*/
|
|
12
|
+
export interface RetryPolicy {
|
|
13
|
+
/** Total attempts including the initial call. Default 3. */
|
|
14
|
+
maxAttempts: number;
|
|
15
|
+
/** Initial backoff in ms. Default 500. */
|
|
16
|
+
baseDelayMs: number;
|
|
17
|
+
/** Multiplier per attempt. Default 2. */
|
|
18
|
+
backoffFactor: number;
|
|
19
|
+
/** Cap on a single delay in ms. Default 10_000. */
|
|
20
|
+
maxDelayMs: number;
|
|
21
|
+
/**
|
|
22
|
+
* Symmetric jitter as a fraction of the computed delay, in `[0, 1)`. The
|
|
23
|
+
* actual delay is `delay * (1 + (rng() - 0.5) * 2 * jitter)`. Default 0.3.
|
|
24
|
+
* Set to 0 to disable.
|
|
25
|
+
*/
|
|
26
|
+
jitter: number;
|
|
27
|
+
}
|
|
28
|
+
export declare const DEFAULT_RETRY_POLICY: RetryPolicy;
|
|
29
|
+
export declare class LLMHttpError extends Error {
|
|
30
|
+
readonly status: number;
|
|
31
|
+
readonly attempts: number;
|
|
32
|
+
/** Full upstream response body (kept on the instance, NOT in `message`). */
|
|
33
|
+
readonly body: string;
|
|
34
|
+
constructor(status: number, body: string, attempts: number);
|
|
35
|
+
}
|
|
36
|
+
export declare function isRetryableStatus(status: number): boolean;
|
|
37
|
+
export interface RunWithRetryArgs<T> {
|
|
38
|
+
policy: RetryPolicy;
|
|
39
|
+
/** Per-attempt callable. Resolves to {result} on success, or returns ok:false to fail. */
|
|
40
|
+
attempt: () => Promise<{
|
|
41
|
+
ok: true;
|
|
42
|
+
value: T;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
status: number;
|
|
46
|
+
body: string;
|
|
47
|
+
retryAfterSeconds?: number;
|
|
48
|
+
}>;
|
|
49
|
+
/** Sleeps for `ms`. Injectable for tests. */
|
|
50
|
+
sleep?: (ms: number) => Promise<void>;
|
|
51
|
+
/** Random source in `[0, 1)`. Injectable for tests. Defaults to `Math.random`. */
|
|
52
|
+
rng?: () => number;
|
|
53
|
+
}
|
|
54
|
+
export declare function runWithRetry<T>(args: RunWithRetryArgs<T>): Promise<T>;
|
|
55
|
+
/** Parses a `Retry-After` header (seconds-only form). */
|
|
56
|
+
export declare function parseRetryAfterSeconds(header: null | string): number | undefined;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared retry helper for LLMClient adapters.
|
|
3
|
+
*
|
|
4
|
+
* Bounded exponential backoff with optional `Retry-After` honoring and
|
|
5
|
+
* symmetric jitter. Treats 429 / 5xx as retryable and any other HTTP error
|
|
6
|
+
* as terminal.
|
|
7
|
+
*
|
|
8
|
+
* Errors carry the full response body on the instance for callers that need
|
|
9
|
+
* to inspect it; the message is intentionally short and body-free so it's
|
|
10
|
+
* safe to include in user-facing logs and stack traces.
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_RETRY_POLICY = {
|
|
13
|
+
maxAttempts: 3,
|
|
14
|
+
baseDelayMs: 500,
|
|
15
|
+
backoffFactor: 2,
|
|
16
|
+
maxDelayMs: 10_000,
|
|
17
|
+
jitter: 0.3,
|
|
18
|
+
};
|
|
19
|
+
export class LLMHttpError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
attempts;
|
|
22
|
+
/** Full upstream response body (kept on the instance, NOT in `message`). */
|
|
23
|
+
body;
|
|
24
|
+
constructor(status, body, attempts) {
|
|
25
|
+
super(`LLM request failed with status ${status} after ${attempts} attempt(s)`);
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.attempts = attempts;
|
|
28
|
+
this.name = "LLMHttpError";
|
|
29
|
+
this.body = body;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function isRetryableStatus(status) {
|
|
33
|
+
return status === 429 || (status >= 500 && status < 600);
|
|
34
|
+
}
|
|
35
|
+
const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
36
|
+
export async function runWithRetry(args) {
|
|
37
|
+
const { policy, attempt, sleep = defaultSleep, rng = Math.random } = args;
|
|
38
|
+
for (let i = 1; i <= policy.maxAttempts; i++) {
|
|
39
|
+
const res = await attempt();
|
|
40
|
+
if (res.ok)
|
|
41
|
+
return res.value;
|
|
42
|
+
const canRetry = i < policy.maxAttempts && isRetryableStatus(res.status);
|
|
43
|
+
if (!canRetry) {
|
|
44
|
+
throw new LLMHttpError(res.status, res.body, i);
|
|
45
|
+
}
|
|
46
|
+
const exp = policy.baseDelayMs * Math.pow(policy.backoffFactor, i - 1);
|
|
47
|
+
const base = res.retryAfterSeconds ? res.retryAfterSeconds * 1000 : exp;
|
|
48
|
+
const capped = Math.min(base, policy.maxDelayMs);
|
|
49
|
+
const jittered = policy.jitter > 0
|
|
50
|
+
? capped * (1 + (rng() - 0.5) * 2 * policy.jitter)
|
|
51
|
+
: capped;
|
|
52
|
+
await sleep(Math.max(0, Math.round(jittered)));
|
|
53
|
+
}
|
|
54
|
+
// Unreachable: the canRetry branch always throws on the final attempt.
|
|
55
|
+
// Defensive throw so the type checker sees a definite return.
|
|
56
|
+
throw new LLMHttpError(0, "no error body", policy.maxAttempts);
|
|
57
|
+
}
|
|
58
|
+
/** Parses a `Retry-After` header (seconds-only form). */
|
|
59
|
+
export function parseRetryAfterSeconds(header) {
|
|
60
|
+
if (!header)
|
|
61
|
+
return undefined;
|
|
62
|
+
const n = Number(header);
|
|
63
|
+
if (Number.isFinite(n) && n >= 0)
|
|
64
|
+
return n;
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
@@ -77,6 +77,7 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
77
77
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
78
78
|
}, z.core.$strip>, z.ZodObject<{
|
|
79
79
|
type: z.ZodEnum<{
|
|
80
|
+
cost: "cost";
|
|
80
81
|
"llm-rubric": "llm-rubric";
|
|
81
82
|
contains: "contains";
|
|
82
83
|
"contains-any": "contains-any";
|
|
@@ -87,7 +88,6 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
87
88
|
regex: "regex";
|
|
88
89
|
javascript: "javascript";
|
|
89
90
|
similar: "similar";
|
|
90
|
-
cost: "cost";
|
|
91
91
|
latency: "latency";
|
|
92
92
|
"file-exists": "file-exists";
|
|
93
93
|
"file-contains": "file-contains";
|
|
@@ -191,6 +191,7 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
191
191
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
192
192
|
}, z.core.$strip>, z.ZodObject<{
|
|
193
193
|
type: z.ZodEnum<{
|
|
194
|
+
cost: "cost";
|
|
194
195
|
"llm-rubric": "llm-rubric";
|
|
195
196
|
contains: "contains";
|
|
196
197
|
"contains-any": "contains-any";
|
|
@@ -201,7 +202,6 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
201
202
|
regex: "regex";
|
|
202
203
|
javascript: "javascript";
|
|
203
204
|
similar: "similar";
|
|
204
|
-
cost: "cost";
|
|
205
205
|
latency: "latency";
|
|
206
206
|
"file-exists": "file-exists";
|
|
207
207
|
"file-contains": "file-contains";
|
|
@@ -345,6 +345,7 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
345
345
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
346
346
|
}, z.core.$strip>, z.ZodObject<{
|
|
347
347
|
type: z.ZodEnum<{
|
|
348
|
+
cost: "cost";
|
|
348
349
|
"llm-rubric": "llm-rubric";
|
|
349
350
|
contains: "contains";
|
|
350
351
|
"contains-any": "contains-any";
|
|
@@ -355,7 +356,6 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
355
356
|
regex: "regex";
|
|
356
357
|
javascript: "javascript";
|
|
357
358
|
similar: "similar";
|
|
358
|
-
cost: "cost";
|
|
359
359
|
latency: "latency";
|
|
360
360
|
"file-exists": "file-exists";
|
|
361
361
|
"file-contains": "file-contains";
|
|
@@ -476,6 +476,7 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
476
476
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
477
477
|
}, z.core.$strip>, z.ZodObject<{
|
|
478
478
|
type: z.ZodEnum<{
|
|
479
|
+
cost: "cost";
|
|
479
480
|
"llm-rubric": "llm-rubric";
|
|
480
481
|
contains: "contains";
|
|
481
482
|
"contains-any": "contains-any";
|
|
@@ -486,7 +487,6 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
486
487
|
regex: "regex";
|
|
487
488
|
javascript: "javascript";
|
|
488
489
|
similar: "similar";
|
|
489
|
-
cost: "cost";
|
|
490
490
|
latency: "latency";
|
|
491
491
|
"file-exists": "file-exists";
|
|
492
492
|
"file-contains": "file-contains";
|
|
@@ -595,6 +595,7 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
595
595
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
596
596
|
}, z.core.$strip>, z.ZodObject<{
|
|
597
597
|
type: z.ZodEnum<{
|
|
598
|
+
cost: "cost";
|
|
598
599
|
"llm-rubric": "llm-rubric";
|
|
599
600
|
contains: "contains";
|
|
600
601
|
"contains-any": "contains-any";
|
|
@@ -605,7 +606,6 @@ export declare const CanonicalTaskSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
605
606
|
regex: "regex";
|
|
606
607
|
javascript: "javascript";
|
|
607
608
|
similar: "similar";
|
|
608
|
-
cost: "cost";
|
|
609
609
|
latency: "latency";
|
|
610
610
|
"file-exists": "file-exists";
|
|
611
611
|
"file-contains": "file-contains";
|
|
@@ -703,6 +703,7 @@ export declare const ContentLakeAuthorableTaskSchema: z.ZodObject<{
|
|
|
703
703
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
704
704
|
}, z.core.$strip>, z.ZodObject<{
|
|
705
705
|
type: z.ZodEnum<{
|
|
706
|
+
cost: "cost";
|
|
706
707
|
"llm-rubric": "llm-rubric";
|
|
707
708
|
contains: "contains";
|
|
708
709
|
"contains-any": "contains-any";
|
|
@@ -713,7 +714,6 @@ export declare const ContentLakeAuthorableTaskSchema: z.ZodObject<{
|
|
|
713
714
|
regex: "regex";
|
|
714
715
|
javascript: "javascript";
|
|
715
716
|
similar: "similar";
|
|
716
|
-
cost: "cost";
|
|
717
717
|
latency: "latency";
|
|
718
718
|
"file-exists": "file-exists";
|
|
719
719
|
"file-contains": "file-contains";
|
|
@@ -823,6 +823,7 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
823
823
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
824
824
|
}, z.core.$strip>, z.ZodObject<{
|
|
825
825
|
type: z.ZodEnum<{
|
|
826
|
+
cost: "cost";
|
|
826
827
|
"llm-rubric": "llm-rubric";
|
|
827
828
|
contains: "contains";
|
|
828
829
|
"contains-any": "contains-any";
|
|
@@ -833,7 +834,6 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
833
834
|
regex: "regex";
|
|
834
835
|
javascript: "javascript";
|
|
835
836
|
similar: "similar";
|
|
836
|
-
cost: "cost";
|
|
837
837
|
latency: "latency";
|
|
838
838
|
"file-exists": "file-exists";
|
|
839
839
|
"file-contains": "file-contains";
|
|
@@ -937,6 +937,7 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
937
937
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
938
938
|
}, z.core.$strip>, z.ZodObject<{
|
|
939
939
|
type: z.ZodEnum<{
|
|
940
|
+
cost: "cost";
|
|
940
941
|
"llm-rubric": "llm-rubric";
|
|
941
942
|
contains: "contains";
|
|
942
943
|
"contains-any": "contains-any";
|
|
@@ -947,7 +948,6 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
947
948
|
regex: "regex";
|
|
948
949
|
javascript: "javascript";
|
|
949
950
|
similar: "similar";
|
|
950
|
-
cost: "cost";
|
|
951
951
|
latency: "latency";
|
|
952
952
|
"file-exists": "file-exists";
|
|
953
953
|
"file-contains": "file-contains";
|
|
@@ -1091,6 +1091,7 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
1091
1091
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
1092
1092
|
}, z.core.$strip>, z.ZodObject<{
|
|
1093
1093
|
type: z.ZodEnum<{
|
|
1094
|
+
cost: "cost";
|
|
1094
1095
|
"llm-rubric": "llm-rubric";
|
|
1095
1096
|
contains: "contains";
|
|
1096
1097
|
"contains-any": "contains-any";
|
|
@@ -1101,7 +1102,6 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
1101
1102
|
regex: "regex";
|
|
1102
1103
|
javascript: "javascript";
|
|
1103
1104
|
similar: "similar";
|
|
1104
|
-
cost: "cost";
|
|
1105
1105
|
latency: "latency";
|
|
1106
1106
|
"file-exists": "file-exists";
|
|
1107
1107
|
"file-contains": "file-contains";
|
|
@@ -1222,6 +1222,7 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
1222
1222
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
1223
1223
|
}, z.core.$strip>, z.ZodObject<{
|
|
1224
1224
|
type: z.ZodEnum<{
|
|
1225
|
+
cost: "cost";
|
|
1225
1226
|
"llm-rubric": "llm-rubric";
|
|
1226
1227
|
contains: "contains";
|
|
1227
1228
|
"contains-any": "contains-any";
|
|
@@ -1232,7 +1233,6 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
1232
1233
|
regex: "regex";
|
|
1233
1234
|
javascript: "javascript";
|
|
1234
1235
|
similar: "similar";
|
|
1235
|
-
cost: "cost";
|
|
1236
1236
|
latency: "latency";
|
|
1237
1237
|
"file-exists": "file-exists";
|
|
1238
1238
|
"file-contains": "file-contains";
|
|
@@ -1341,6 +1341,7 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
1341
1341
|
weight: z.ZodOptional<z.ZodNumber>;
|
|
1342
1342
|
}, z.core.$strip>, z.ZodObject<{
|
|
1343
1343
|
type: z.ZodEnum<{
|
|
1344
|
+
cost: "cost";
|
|
1344
1345
|
"llm-rubric": "llm-rubric";
|
|
1345
1346
|
contains: "contains";
|
|
1346
1347
|
"contains-any": "contains-any";
|
|
@@ -1351,7 +1352,6 @@ export declare const CanonicalTaskFileSchema: z.ZodArray<z.ZodDiscriminatedUnion
|
|
|
1351
1352
|
regex: "regex";
|
|
1352
1353
|
javascript: "javascript";
|
|
1353
1354
|
similar: "similar";
|
|
1354
|
-
cost: "cost";
|
|
1355
1355
|
latency: "latency";
|
|
1356
1356
|
"file-exists": "file-exists";
|
|
1357
1357
|
"file-contains": "file-contains";
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* @see docs/decisions/D0032-run-anchored-artifact-store.md
|
|
28
28
|
* @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md
|
|
29
29
|
*/
|
|
30
|
-
import { ARTIFACT_REGISTRY, NotImplementedError, } from "../_vendor/ailf-core/index.js";
|
|
30
|
+
import { ARTIFACT_REGISTRY, assertWritePolicyMatches, NotImplementedError, } from "../_vendor/ailf-core/index.js";
|
|
31
31
|
import { prepareUploadBody } from "./prepare-upload-body.js";
|
|
32
32
|
import { NO_OP_UPLOAD_METRICS, } from "./upload-metrics.js";
|
|
33
33
|
export class ApiGatewayArtifactWriter {
|
|
@@ -40,6 +40,7 @@ export class ApiGatewayArtifactWriter {
|
|
|
40
40
|
// ---- Canonical W0049 API ------------------------------------------------
|
|
41
41
|
async emit(type, association, payload) {
|
|
42
42
|
const descriptor = ARTIFACT_REGISTRY[type];
|
|
43
|
+
assertWritePolicyMatches("pipeline", descriptor);
|
|
43
44
|
const runId = association.run;
|
|
44
45
|
if (!runId) {
|
|
45
46
|
console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* does this writer. Traces flow through the GCS-direct writer when ADC
|
|
26
26
|
* credentials are present.
|
|
27
27
|
*/
|
|
28
|
-
import { ARTIFACT_REGISTRY, BULK_ENTRY_KEY, NotImplementedError, } from "../_vendor/ailf-core/index.js";
|
|
28
|
+
import { ARTIFACT_REGISTRY, assertWritePolicyMatches, BULK_ENTRY_KEY, NotImplementedError, } from "../_vendor/ailf-core/index.js";
|
|
29
29
|
import { prepareUploadBody } from "./prepare-upload-body.js";
|
|
30
30
|
import { NO_OP_UPLOAD_METRICS, } from "./upload-metrics.js";
|
|
31
31
|
/**
|
|
@@ -64,6 +64,7 @@ export class BatchingApiGatewayArtifactWriter {
|
|
|
64
64
|
// ---- ArtifactWriter surface --------------------------------------------
|
|
65
65
|
async emit(type, association, payload) {
|
|
66
66
|
const descriptor = ARTIFACT_REGISTRY[type];
|
|
67
|
+
assertWritePolicyMatches("pipeline", descriptor);
|
|
67
68
|
const runId = association.run;
|
|
68
69
|
if (!runId) {
|
|
69
70
|
console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* @see docs/decisions/D0033-unified-run-anchored-artifact-capture.md
|
|
29
29
|
*/
|
|
30
30
|
import { Storage } from "@google-cloud/storage";
|
|
31
|
-
import { ARTIFACT_REGISTRY, buildManifestPreview, } from "../_vendor/ailf-core/index.js";
|
|
31
|
+
import { ARTIFACT_REGISTRY, assertWritePolicyMatches, buildManifestPreview, } from "../_vendor/ailf-core/index.js";
|
|
32
32
|
import { resolveUploadConcurrency } from "./parallel-emit.js";
|
|
33
33
|
import { prepareUploadBody } from "./prepare-upload-body.js";
|
|
34
34
|
import { redactArtifactData } from "./redact-artifact.js";
|
|
@@ -79,6 +79,7 @@ export class GcsArtifactWriter {
|
|
|
79
79
|
// ---- Canonical W0049 API ------------------------------------------------
|
|
80
80
|
async emit(type, association, payload) {
|
|
81
81
|
const descriptor = ARTIFACT_REGISTRY[type];
|
|
82
|
+
assertWritePolicyMatches("pipeline", descriptor);
|
|
82
83
|
const runId = association.run;
|
|
83
84
|
if (!runId) {
|
|
84
85
|
console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
|
|
@@ -132,6 +133,7 @@ export class GcsArtifactWriter {
|
|
|
132
133
|
}
|
|
133
134
|
async appendNdjson(type, association, rows) {
|
|
134
135
|
const descriptor = ARTIFACT_REGISTRY[type];
|
|
136
|
+
assertWritePolicyMatches("pipeline", descriptor);
|
|
135
137
|
if (descriptor.mime !== "application/x-ndjson") {
|
|
136
138
|
console.warn(` ⚠️ appendNdjson("${type}"): descriptor mime is ${descriptor.mime}, not application/x-ndjson — skipping`);
|
|
137
139
|
return null;
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import { promises as fs } from "node:fs";
|
|
40
40
|
import path from "node:path";
|
|
41
|
-
import { ARTIFACT_REGISTRY, buildManifestPreview, } from "../_vendor/ailf-core/index.js";
|
|
41
|
+
import { ARTIFACT_REGISTRY, assertWritePolicyMatches, buildManifestPreview, } from "../_vendor/ailf-core/index.js";
|
|
42
42
|
import { redactArtifactData } from "./redact-artifact.js";
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
// Implementation
|
|
@@ -66,6 +66,7 @@ export class LocalFilesystemArtifactWriter {
|
|
|
66
66
|
if (this.excludeSet.has(type))
|
|
67
67
|
return null;
|
|
68
68
|
const descriptor = ARTIFACT_REGISTRY[type];
|
|
69
|
+
assertWritePolicyMatches("pipeline", descriptor);
|
|
69
70
|
const runId = association.run;
|
|
70
71
|
if (!runId) {
|
|
71
72
|
console.warn(` ⚠️ emit("${type}"): association.run is required, skipping`);
|
|
@@ -127,6 +128,7 @@ export class LocalFilesystemArtifactWriter {
|
|
|
127
128
|
if (this.excludeSet.has(type))
|
|
128
129
|
return null;
|
|
129
130
|
const descriptor = ARTIFACT_REGISTRY[type];
|
|
131
|
+
assertWritePolicyMatches("pipeline", descriptor);
|
|
130
132
|
if (descriptor.mime !== "application/x-ndjson") {
|
|
131
133
|
console.warn(` ⚠️ appendNdjson("${type}"): descriptor mime is ${descriptor.mime}, not application/x-ndjson — skipping`);
|
|
132
134
|
return null;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* @see packages/core/src/ports/context.ts — AppContext interface
|
|
16
16
|
* @see docs/archive/exec-plans/ports-and-adapters/phase-7-composition-root.md
|
|
17
17
|
*/
|
|
18
|
-
import { type AppContext, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssertionRegistration, type Logger, type ResolvedConfig } from "./_vendor/ailf-core/index.d.ts";
|
|
18
|
+
import { type AppContext, type ArtifactWriter, type ArtifactWriterProgressOptions, type AssertionRegistration, type LLMClient, type Logger, type ResolvedConfig } from "./_vendor/ailf-core/index.d.ts";
|
|
19
19
|
import { CompositeTaskSource, ContentLakeTaskSource, RepoTaskSource } from "./adapters/task-sources/index.js";
|
|
20
20
|
/**
|
|
21
21
|
* Create a fully wired AppContext from resolved configuration.
|
|
@@ -24,6 +24,28 @@ import { CompositeTaskSource, ContentLakeTaskSource, RepoTaskSource } from "./ad
|
|
|
24
24
|
* Swapping an adapter is a one-line change in this function.
|
|
25
25
|
*/
|
|
26
26
|
export declare function createAppContext(config: ResolvedConfig): AppContext;
|
|
27
|
+
/**
|
|
28
|
+
* Typed key bag passed to `createLLMClient`. The composition root reads
|
|
29
|
+
* env once and supplies values here; the factory stays pure so tests don't
|
|
30
|
+
* have to mutate `process.env`.
|
|
31
|
+
*/
|
|
32
|
+
export interface LLMClientKeys {
|
|
33
|
+
anthropicApiKey?: string;
|
|
34
|
+
openaiApiKey?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Select the LLMClient adapter based on `config.llmProvider` and the
|
|
38
|
+
* supplied API keys. Returns `undefined` when no usable credential is
|
|
39
|
+
* present — `AppContext.llmClient` stays unset and consumers handle that
|
|
40
|
+
* explicitly.
|
|
41
|
+
*
|
|
42
|
+
* Adapters never read `process.env` themselves (per
|
|
43
|
+
* `.claude/rules/typescript.md`); env mapping happens at the call site
|
|
44
|
+
* (typically `createAppContext`).
|
|
45
|
+
*
|
|
46
|
+
* Exported for unit-test access; not part of the public package API.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createLLMClient(config: ResolvedConfig, keys: LLMClientKeys, logger: Logger): LLMClient | undefined;
|
|
27
49
|
/**
|
|
28
50
|
* Selects the `ArtifactWriter` wiring per D0033 M4:
|
|
29
51
|
*
|
package/dist/composition-root.js
CHANGED
|
@@ -26,6 +26,7 @@ import { LocalFilesystemArtifactWriter } from "./artifact-capture/local-fs-artif
|
|
|
26
26
|
import { resolveUploadConcurrency, setDefaultUploadConcurrency, } from "./artifact-capture/parallel-emit.js";
|
|
27
27
|
import { UploadMetrics } from "./artifact-capture/upload-metrics.js";
|
|
28
28
|
import { ContentLakeCacheAdapter } from "./adapters/cache/content-lake-cache.js";
|
|
29
|
+
import { AnthropicLLMClient, OpenAILLMClient } from "./adapters/llm/index.js";
|
|
29
30
|
import { loadExternalPresets } from "./pipeline/compiler/preset-loader.js";
|
|
30
31
|
import { FilesystemCache } from "./adapters/cache/filesystem-cache.js";
|
|
31
32
|
import { PromptfooEvalAdapter } from "./adapters/eval-runners/promptfoo-eval-adapter.js";
|
|
@@ -91,12 +92,20 @@ export function createAppContext(config) {
|
|
|
91
92
|
// from the context (D0032).
|
|
92
93
|
const runId = generateRunId();
|
|
93
94
|
logger.debug(`Pipeline runId: ${runId}`);
|
|
95
|
+
// LLM client (D0051) — wired when an API key is present. The grader path
|
|
96
|
+
// does NOT consume this; D0051 defers grader migration as a follow-up.
|
|
97
|
+
// Env mapping happens here so `createLLMClient` stays pure and testable.
|
|
98
|
+
const llmClient = createLLMClient(config, {
|
|
99
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
100
|
+
openaiApiKey: process.env.OPENAI_API_KEY,
|
|
101
|
+
}, logger);
|
|
94
102
|
return {
|
|
95
103
|
artifactWriter,
|
|
96
104
|
cache,
|
|
97
105
|
config,
|
|
98
106
|
docFetcher,
|
|
99
107
|
evalRunner,
|
|
108
|
+
...(llmClient ? { llmClient } : {}),
|
|
100
109
|
logger,
|
|
101
110
|
packageSurfaceResolver,
|
|
102
111
|
progress,
|
|
@@ -107,6 +116,44 @@ export function createAppContext(config) {
|
|
|
107
116
|
taskSource,
|
|
108
117
|
};
|
|
109
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Select the LLMClient adapter based on `config.llmProvider` and the
|
|
121
|
+
* supplied API keys. Returns `undefined` when no usable credential is
|
|
122
|
+
* present — `AppContext.llmClient` stays unset and consumers handle that
|
|
123
|
+
* explicitly.
|
|
124
|
+
*
|
|
125
|
+
* Adapters never read `process.env` themselves (per
|
|
126
|
+
* `.claude/rules/typescript.md`); env mapping happens at the call site
|
|
127
|
+
* (typically `createAppContext`).
|
|
128
|
+
*
|
|
129
|
+
* Exported for unit-test access; not part of the public package API.
|
|
130
|
+
*/
|
|
131
|
+
export function createLLMClient(config, keys, logger) {
|
|
132
|
+
const explicit = config.llmProvider;
|
|
133
|
+
const anthropicKey = keys.anthropicApiKey;
|
|
134
|
+
const openaiKey = keys.openaiApiKey;
|
|
135
|
+
// Auto-select: prefer Anthropic when both are present (matches the
|
|
136
|
+
// current grader's default model in `config/models.ts`).
|
|
137
|
+
const provider = explicit ?? (anthropicKey ? "anthropic" : openaiKey ? "openai" : undefined);
|
|
138
|
+
if (!provider) {
|
|
139
|
+
logger.debug("LLM client: not wired — no Anthropic or OpenAI API key supplied");
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
if (provider === "anthropic") {
|
|
143
|
+
if (!anthropicKey) {
|
|
144
|
+
logger.warn('llmProvider="anthropic" but no Anthropic API key supplied — LLMClient not wired');
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
logger.debug("LLM client: AnthropicLLMClient");
|
|
148
|
+
return new AnthropicLLMClient({ apiKey: anthropicKey, logger });
|
|
149
|
+
}
|
|
150
|
+
if (!openaiKey) {
|
|
151
|
+
logger.warn('llmProvider="openai" but no OpenAI API key supplied — LLMClient not wired');
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
logger.debug("LLM client: OpenAILLMClient");
|
|
155
|
+
return new OpenAILLMClient({ apiKey: openaiKey, logger });
|
|
156
|
+
}
|
|
110
157
|
// ---------------------------------------------------------------------------
|
|
111
158
|
// Sub-factories (extracted to keep createAppContext readable)
|
|
112
159
|
// ---------------------------------------------------------------------------
|