@inflexa-ai/harness 0.7.2 → 0.8.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.
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { type LanguageModel } from "ai";
|
|
2
2
|
import type { ResolveBilling } from "../billing/resolver.js";
|
|
3
|
+
import type { Logger } from "../lib/logger.js";
|
|
3
4
|
import type { ChatProvider, FetchLike, ProviderCapabilities } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* The harness-owned retry envelope. The AI SDK's built-in retry exposes only a
|
|
7
|
+
* count: its 2s initial delay and ×2 factor are fixed, it caps neither the
|
|
8
|
+
* per-attempt delay nor the total, and it ignores a server-named `Retry-After`.
|
|
9
|
+
* Wrapping each wire attempt in `retryWithExponentialBackoff` with these values
|
|
10
|
+
* gives a bounded, jittered envelope with a hard per-attempt delay ceiling.
|
|
11
|
+
*/
|
|
12
|
+
export declare const RETRY_MAX_RETRIES = 10;
|
|
13
|
+
export declare const RETRY_INITIAL_DELAY_MS = 2000;
|
|
14
|
+
export declare const RETRY_BACKOFF_FACTOR = 2;
|
|
15
|
+
export declare const RETRY_MAX_DELAY_MS = 30000;
|
|
4
16
|
export interface AiSdkProviderDeps {
|
|
5
17
|
readonly model: LanguageModel;
|
|
6
18
|
readonly resolveBilling: ResolveBilling;
|
|
7
19
|
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
20
|
+
readonly logger?: Logger;
|
|
8
21
|
}
|
|
9
22
|
/**
|
|
10
23
|
* Connection + model configuration for one `ChatProvider`, discriminated by
|
|
@@ -35,7 +48,17 @@ export type AiSdkProviderConfig = {
|
|
|
35
48
|
export interface ConfiguredAiSdkProviderDeps {
|
|
36
49
|
readonly config: AiSdkProviderConfig;
|
|
37
50
|
readonly resolveBilling: ResolveBilling;
|
|
51
|
+
readonly logger?: Logger;
|
|
38
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The delay before the next retry attempt. A server-named `Retry-After` inside
|
|
55
|
+
* the envelope is honored exactly — the server pointed at a precise time, so
|
|
56
|
+
* jitter would only fight it. Otherwise apply full jitter over the exponential
|
|
57
|
+
* delay capped at `RETRY_MAX_DELAY_MS`: full jitter spreads a crowd of callers
|
|
58
|
+
* that failed at the same instant across the window instead of re-colliding on
|
|
59
|
+
* the same retry tick. `random` is injectable so the delay is testable.
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeRetryDelayMs(error: unknown, exponentialBackoffDelay: number, random?: () => number): number;
|
|
39
62
|
export declare function createAiSdkProvider(deps: AiSdkProviderDeps): ChatProvider;
|
|
40
63
|
/**
|
|
41
64
|
* Realize an `AiSdkProviderConfig` into a `ChatProvider` bound to that config's
|
package/dist/providers/ai-sdk.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { generateText, streamText } from "ai";
|
|
2
2
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3
3
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
4
|
+
import { APICallError } from "@ai-sdk/provider";
|
|
5
|
+
import { retryWithExponentialBackoff } from "@ai-sdk/provider-utils";
|
|
4
6
|
import { ResultAsync, err, ok } from "neverthrow";
|
|
5
7
|
import { scopeWorkloadId } from "../auth/types.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
9
|
+
import { classifyProviderError, toProviderError } from "./errors.js";
|
|
10
|
+
/**
|
|
11
|
+
* The harness-owned retry envelope. The AI SDK's built-in retry exposes only a
|
|
12
|
+
* count: its 2s initial delay and ×2 factor are fixed, it caps neither the
|
|
13
|
+
* per-attempt delay nor the total, and it ignores a server-named `Retry-After`.
|
|
14
|
+
* Wrapping each wire attempt in `retryWithExponentialBackoff` with these values
|
|
15
|
+
* gives a bounded, jittered envelope with a hard per-attempt delay ceiling.
|
|
16
|
+
*/
|
|
17
|
+
export const RETRY_MAX_RETRIES = 10;
|
|
18
|
+
export const RETRY_INITIAL_DELAY_MS = 2_000;
|
|
19
|
+
export const RETRY_BACKOFF_FACTOR = 2;
|
|
20
|
+
export const RETRY_MAX_DELAY_MS = 30_000;
|
|
8
21
|
function workloadOf(session) {
|
|
9
22
|
return `${session.scope.kind}:${scopeWorkloadId(session.scope)}`;
|
|
10
23
|
}
|
|
@@ -15,6 +28,153 @@ function isAbortError(value) {
|
|
|
15
28
|
return true;
|
|
16
29
|
return false;
|
|
17
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Locate an `APICallError` on the throwable itself or one hop down its `cause`.
|
|
33
|
+
* A wire failure the SDK raises is an `APICallError` directly; once rewrapped it
|
|
34
|
+
* sits on the wrapper's `cause`, which is as deep as a `Retry-After` header is
|
|
35
|
+
* ever carried.
|
|
36
|
+
*/
|
|
37
|
+
function apiCallErrorOf(error) {
|
|
38
|
+
if (APICallError.isInstance(error))
|
|
39
|
+
return error;
|
|
40
|
+
const cause = error?.cause;
|
|
41
|
+
if (APICallError.isInstance(cause))
|
|
42
|
+
return cause;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
/** Case-insensitive header lookup; `name` must already be lowercase. */
|
|
46
|
+
function headerValue(headers, name) {
|
|
47
|
+
if (headers === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
const direct = headers[name];
|
|
50
|
+
if (direct !== undefined)
|
|
51
|
+
return direct;
|
|
52
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
53
|
+
if (key.toLowerCase() === name)
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/** Parse a header to a finite number, or `undefined` when it is not numeric. */
|
|
59
|
+
function finiteNumber(value) {
|
|
60
|
+
const trimmed = value.trim();
|
|
61
|
+
if (trimmed === "")
|
|
62
|
+
return undefined;
|
|
63
|
+
const n = Number(trimmed);
|
|
64
|
+
return Number.isFinite(n) ? n : undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The delay a server named via `Retry-After`, in ms, or `undefined` when none
|
|
68
|
+
* applies. `retry-after-ms` is float ms; `retry-after` is float seconds or an
|
|
69
|
+
* HTTP date read relative to now. A value outside `[0, RETRY_MAX_DELAY_MS]` is
|
|
70
|
+
* treated as absent so the jittered exponential delay governs instead — this
|
|
71
|
+
* envelope does not honor a wait longer than its own ceiling verbatim.
|
|
72
|
+
*/
|
|
73
|
+
function serverNamedDelayMs(error) {
|
|
74
|
+
const headers = apiCallErrorOf(error)?.responseHeaders;
|
|
75
|
+
if (headers === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
let ms;
|
|
78
|
+
const retryAfterMs = headerValue(headers, "retry-after-ms");
|
|
79
|
+
if (retryAfterMs !== undefined)
|
|
80
|
+
ms = finiteNumber(retryAfterMs);
|
|
81
|
+
if (ms === undefined) {
|
|
82
|
+
const retryAfter = headerValue(headers, "retry-after");
|
|
83
|
+
if (retryAfter !== undefined) {
|
|
84
|
+
const seconds = finiteNumber(retryAfter);
|
|
85
|
+
if (seconds !== undefined) {
|
|
86
|
+
ms = seconds * 1_000;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const at = Date.parse(retryAfter);
|
|
90
|
+
if (Number.isFinite(at))
|
|
91
|
+
ms = at - Date.now();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (ms === undefined || ms < 0 || ms > RETRY_MAX_DELAY_MS)
|
|
96
|
+
return undefined;
|
|
97
|
+
return ms;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The delay before the next retry attempt. A server-named `Retry-After` inside
|
|
101
|
+
* the envelope is honored exactly — the server pointed at a precise time, so
|
|
102
|
+
* jitter would only fight it. Otherwise apply full jitter over the exponential
|
|
103
|
+
* delay capped at `RETRY_MAX_DELAY_MS`: full jitter spreads a crowd of callers
|
|
104
|
+
* that failed at the same instant across the window instead of re-colliding on
|
|
105
|
+
* the same retry tick. `random` is injectable so the delay is testable.
|
|
106
|
+
*/
|
|
107
|
+
export function computeRetryDelayMs(error, exponentialBackoffDelay, random = Math.random) {
|
|
108
|
+
const named = serverNamedDelayMs(error);
|
|
109
|
+
if (named !== undefined)
|
|
110
|
+
return named;
|
|
111
|
+
return random() * Math.min(exponentialBackoffDelay, RETRY_MAX_DELAY_MS);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Marks a failure that came from the `ResolveBilling` seam rather than the model
|
|
115
|
+
* wire. The retry envelope's budget is sized for model-provider outages; the
|
|
116
|
+
* billing seam is a separate system whose failure must surface immediately, not
|
|
117
|
+
* after a multi-minute retry window — even when the underlying failure is
|
|
118
|
+
* connection-shaped and would otherwise read as retryable. The original throwable
|
|
119
|
+
* rides on `cause` so the outer catch can hand it to `toProviderError` unchanged
|
|
120
|
+
* and classify it byte-identically to a billing failure raised outside the
|
|
121
|
+
* envelope (right down to the surfaced message).
|
|
122
|
+
*/
|
|
123
|
+
class BillingSeamFailure extends Error {
|
|
124
|
+
constructor(cause) {
|
|
125
|
+
super("billing resolution failed", { cause });
|
|
126
|
+
this.name = "BillingSeamFailure";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolve attribution headers for one attempt, tagging any non-abort failure as
|
|
131
|
+
* a `BillingSeamFailure` so the retry envelope fails fast on it. An abort is
|
|
132
|
+
* rethrown raw so it stays on the abort control-flow path.
|
|
133
|
+
*/
|
|
134
|
+
async function resolveBillingHeaders(resolveBilling, session) {
|
|
135
|
+
try {
|
|
136
|
+
return await resolveBilling(session);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
if (isAbortError(e))
|
|
140
|
+
throw e;
|
|
141
|
+
throw new BillingSeamFailure(e);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Unwrap a billing-seam failure to the original throwable for classification. */
|
|
145
|
+
function unwrapForClassification(e) {
|
|
146
|
+
return e instanceof BillingSeamFailure ? e.cause : e;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Wrap a single wire attempt in the harness retry envelope. Built per call so
|
|
150
|
+
* the caller's `AbortSignal` reaches the primitive: it rethrows abort errors
|
|
151
|
+
* without retrying and cancels its own backoff sleep when the signal fires, so
|
|
152
|
+
* the retried closure adds no abort handling of its own. `shouldRetry` defers to
|
|
153
|
+
* the harness retryability taxonomy — but never retries a `BillingSeamFailure`,
|
|
154
|
+
* whose fail-fast is the whole point of the marker — and a non-retried first
|
|
155
|
+
* failure is rethrown untouched, preserving the exact throwable the outer catch
|
|
156
|
+
* classifies. `createRetryError` carries the last real failure on `cause` so
|
|
157
|
+
* that, once retries are exhausted, `toProviderError`'s status walk reaches the
|
|
158
|
+
* true HTTP status instead of stopping at a synthetic wrapper. The attempt
|
|
159
|
+
* counter is per-instance because each call builds its own retry.
|
|
160
|
+
*/
|
|
161
|
+
function createRetry(signal, logger) {
|
|
162
|
+
let retryCount = 0;
|
|
163
|
+
return retryWithExponentialBackoff({
|
|
164
|
+
maxRetries: RETRY_MAX_RETRIES,
|
|
165
|
+
initialDelayInMs: RETRY_INITIAL_DELAY_MS,
|
|
166
|
+
backoffFactor: RETRY_BACKOFF_FACTOR,
|
|
167
|
+
abortSignal: signal,
|
|
168
|
+
shouldRetry: (e) => !(e instanceof BillingSeamFailure) && classifyProviderError(e).retryable,
|
|
169
|
+
getDelayInMs: ({ error, exponentialBackoffDelay }) => {
|
|
170
|
+
const delayMs = computeRetryDelayMs(error, exponentialBackoffDelay);
|
|
171
|
+
retryCount += 1;
|
|
172
|
+
logger.debug("retrying provider call", { attempt: retryCount, delayMs, ...logger.errorFields(error) });
|
|
173
|
+
return delayMs;
|
|
174
|
+
},
|
|
175
|
+
createRetryError: ({ message, errors }) => new Error(message, { cause: errors[errors.length - 1] }),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
18
178
|
/**
|
|
19
179
|
* Map AI SDK token accounting onto the harness's neutral `ChatUsage`.
|
|
20
180
|
*
|
|
@@ -85,51 +245,101 @@ function sanitizeMessages(messages) {
|
|
|
85
245
|
}
|
|
86
246
|
export function createAiSdkProvider(deps) {
|
|
87
247
|
const capabilities = { toolCalling: deps.capabilities?.toolCalling ?? true };
|
|
248
|
+
const logger = (deps.logger ?? createNoopLogger()).named("providers.ai-sdk");
|
|
88
249
|
function chat(req, session, signal) {
|
|
89
250
|
const run = async () => {
|
|
251
|
+
const retry = createRetry(signal, logger);
|
|
90
252
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
253
|
+
const result = await retry(async () => {
|
|
254
|
+
// Attribution headers are time-limited; resolving them inside the
|
|
255
|
+
// retried closure keeps them fresh across a multi-minute window.
|
|
256
|
+
const headers = await resolveBillingHeaders(deps.resolveBilling, session);
|
|
257
|
+
return generateText({
|
|
258
|
+
model: deps.model,
|
|
259
|
+
system: req.system,
|
|
260
|
+
messages: sanitizeMessages(req.messages),
|
|
261
|
+
tools: req.tools,
|
|
262
|
+
toolChoice: req.toolChoice ?? "auto",
|
|
263
|
+
stopWhen: [],
|
|
264
|
+
// The harness envelope owns retries; leaving the SDK default in
|
|
265
|
+
// place would multiply attempts (10 outer × 3 inner).
|
|
266
|
+
maxRetries: 0,
|
|
267
|
+
headers,
|
|
268
|
+
abortSignal: signal,
|
|
269
|
+
providerOptions: req.providerOptions,
|
|
270
|
+
});
|
|
103
271
|
});
|
|
104
272
|
return ok(responseFromGenerate(result));
|
|
105
273
|
}
|
|
106
274
|
catch (e) {
|
|
107
275
|
if (isAbortError(e) || signal?.aborted)
|
|
108
276
|
throw e;
|
|
109
|
-
return err(toProviderError(e, workloadOf(session)));
|
|
277
|
+
return err(toProviderError(unwrapForClassification(e), workloadOf(session)));
|
|
110
278
|
}
|
|
111
279
|
};
|
|
112
280
|
return new ResultAsync(run());
|
|
113
281
|
}
|
|
114
282
|
async function* chatStream(req, session, signal) {
|
|
283
|
+
const retry = createRetry(signal, logger);
|
|
115
284
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
headers,
|
|
126
|
-
|
|
127
|
-
|
|
285
|
+
// Retry covers only stream establishment: streamText defers wire errors
|
|
286
|
+
// to consumption, so a failure is not visible until the stream is read.
|
|
287
|
+
// The retried closure resolves to one of two outcomes — the stream is
|
|
288
|
+
// live with a first delta in hand, or it finished without yielding any
|
|
289
|
+
// text — and a delta is only ever yielded OUTSIDE the closure, so a
|
|
290
|
+
// retried attempt can never re-emit a delta the consumer already saw.
|
|
291
|
+
// Resolving billing inside the closure keeps attribution headers fresh
|
|
292
|
+
// per attempt.
|
|
293
|
+
const opened = await retry(async () => {
|
|
294
|
+
const headers = await resolveBillingHeaders(deps.resolveBilling, session);
|
|
295
|
+
const result = streamText({
|
|
296
|
+
model: deps.model,
|
|
297
|
+
system: req.system,
|
|
298
|
+
messages: sanitizeMessages(req.messages),
|
|
299
|
+
tools: req.tools,
|
|
300
|
+
toolChoice: req.toolChoice ?? "auto",
|
|
301
|
+
stopWhen: [],
|
|
302
|
+
maxRetries: 0,
|
|
303
|
+
headers,
|
|
304
|
+
abortSignal: signal,
|
|
305
|
+
providerOptions: req.providerOptions,
|
|
306
|
+
});
|
|
307
|
+
const iterator = result.textStream[Symbol.asyncIterator]();
|
|
308
|
+
const first = await iterator.next();
|
|
309
|
+
if (first.done) {
|
|
310
|
+
// A doStream promise rejection — the shape a real connection
|
|
311
|
+
// failure takes — does NOT surface at this first pull: streamText
|
|
312
|
+
// resolves it `{ done: true }` and defers the rejection to the
|
|
313
|
+
// deferred result promises. Awaiting them here brings that failure
|
|
314
|
+
// inside the retry envelope so a text-less establishment error is
|
|
315
|
+
// retried; a genuine text-less turn resolves them, and the terminal
|
|
316
|
+
// event is built from these already-resolved values without a
|
|
317
|
+
// second await. (These promises only settle once the stream is
|
|
318
|
+
// fully drained, so they can be awaited here only because the first
|
|
319
|
+
// pull already reported the stream done.)
|
|
320
|
+
return {
|
|
321
|
+
kind: "completed",
|
|
322
|
+
messages: await result.responseMessages,
|
|
323
|
+
finishReason: await result.finishReason,
|
|
324
|
+
rawFinishReason: await result.rawFinishReason,
|
|
325
|
+
usage: await result.usage,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return { kind: "streaming", result, iterator, firstDelta: first.value };
|
|
128
329
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
330
|
+
if (opened.kind === "completed") {
|
|
331
|
+
const response = responseFromMessages(opened.messages, "", opened.finishReason, opened.rawFinishReason, opened.usage);
|
|
332
|
+
yield { type: "done", response };
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const { result, iterator, firstDelta } = opened;
|
|
336
|
+
let text = firstDelta;
|
|
337
|
+
yield { type: "text-delta", text: firstDelta };
|
|
338
|
+
// Drain the same iterator: past the first delta, errors propagate
|
|
339
|
+
// unchanged (no retry, no re-emit).
|
|
340
|
+
for (let next = await iterator.next(); !next.done; next = await iterator.next()) {
|
|
341
|
+
text += next.value;
|
|
342
|
+
yield { type: "text-delta", text: next.value };
|
|
133
343
|
}
|
|
134
344
|
const response = responseFromMessages(await result.responseMessages, text, await result.finishReason, await result.rawFinishReason, await result.usage);
|
|
135
345
|
yield { type: "done", response };
|
|
@@ -137,7 +347,7 @@ export function createAiSdkProvider(deps) {
|
|
|
137
347
|
catch (e) {
|
|
138
348
|
if (isAbortError(e) || signal?.aborted)
|
|
139
349
|
throw e;
|
|
140
|
-
throw toProviderError(e, workloadOf(session));
|
|
350
|
+
throw toProviderError(unwrapForClassification(e), workloadOf(session));
|
|
141
351
|
}
|
|
142
352
|
}
|
|
143
353
|
return { capabilities, chat, chatStream };
|
|
@@ -157,7 +367,12 @@ export function createConfiguredAiSdkProvider(deps) {
|
|
|
157
367
|
apiKey: config.apiKey,
|
|
158
368
|
fetch: config.fetch,
|
|
159
369
|
});
|
|
160
|
-
return createAiSdkProvider({
|
|
370
|
+
return createAiSdkProvider({
|
|
371
|
+
model: provider.chat(config.model),
|
|
372
|
+
resolveBilling: deps.resolveBilling,
|
|
373
|
+
capabilities: config.capabilities,
|
|
374
|
+
logger: deps.logger,
|
|
375
|
+
});
|
|
161
376
|
}
|
|
162
377
|
const provider = createOpenAICompatible({
|
|
163
378
|
name: config.name,
|
|
@@ -165,5 +380,10 @@ export function createConfiguredAiSdkProvider(deps) {
|
|
|
165
380
|
apiKey: config.apiKey,
|
|
166
381
|
fetch: config.fetch,
|
|
167
382
|
});
|
|
168
|
-
return createAiSdkProvider({
|
|
383
|
+
return createAiSdkProvider({
|
|
384
|
+
model: provider.chatModel(config.model),
|
|
385
|
+
resolveBilling: deps.resolveBilling,
|
|
386
|
+
capabilities: config.capabilities,
|
|
387
|
+
logger: deps.logger,
|
|
388
|
+
});
|
|
169
389
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* old factory name for embedders while returning the new `ChatProvider` seam.
|
|
6
6
|
*/
|
|
7
7
|
import type { ResolveBilling } from "../billing/resolver.js";
|
|
8
|
+
import type { Logger } from "../lib/logger.js";
|
|
8
9
|
import type { ChatProvider, FetchLike } from "./types.js";
|
|
9
10
|
export interface AnthropicProviderDeps {
|
|
10
11
|
readonly baseURL?: string;
|
|
@@ -12,6 +13,7 @@ export interface AnthropicProviderDeps {
|
|
|
12
13
|
readonly model: string;
|
|
13
14
|
readonly resolveBilling: ResolveBilling;
|
|
14
15
|
readonly fetch?: FetchLike;
|
|
16
|
+
readonly logger?: Logger;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Convenience over the `anthropic` arm of the public `AiSdkProviderConfig`
|
|
@@ -14,6 +14,7 @@ import { createConfiguredAiSdkProvider } from "./ai-sdk.js";
|
|
|
14
14
|
export function createAnthropicProvider(deps) {
|
|
15
15
|
return createConfiguredAiSdkProvider({
|
|
16
16
|
resolveBilling: deps.resolveBilling,
|
|
17
|
+
logger: deps.logger,
|
|
17
18
|
config: {
|
|
18
19
|
kind: "anthropic",
|
|
19
20
|
baseURL: deps.baseURL,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inflexa-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Host-agnostic bioinformatics agent harness \u2014 hand-rolled agent loop, DBOS-durable workflows, sandboxed compute.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@ai-sdk/anthropic": "^4.0.15",
|
|
48
48
|
"@ai-sdk/openai-compatible": "^3.0.10",
|
|
49
49
|
"@ai-sdk/provider": "^4.0.3",
|
|
50
|
+
"@ai-sdk/provider-utils": "5.0.10",
|
|
50
51
|
"@anthropic-ai/sdk": "^0.107.0",
|
|
51
52
|
"@dbos-inc/dbos-sdk": "^4.23.6",
|
|
52
53
|
"@kubernetes/client-node": "^1.4.0",
|