@inflexa-ai/harness 0.37.0 → 0.38.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/README.md +1 -2
- package/dist/auth/local-run-authorizer.js +5 -4
- package/dist/auth/types.d.ts +28 -9
- package/dist/auth/types.js +5 -3
- package/dist/billing/noop-run-charge.js +3 -2
- package/dist/billing/noop-usage-recorder.js +2 -1
- package/dist/billing/run-charge.d.ts +18 -5
- package/dist/billing/run-charge.js +5 -1
- package/dist/billing/usage-recorder.d.ts +11 -9
- package/dist/billing/usage-recorder.js +2 -3
- package/dist/contracts/chat-events.d.ts +4 -1
- package/dist/contracts/chat-parts.d.ts +1 -1
- package/dist/contracts/schemas/chat-parts.d.ts +4 -4
- package/dist/execution/artifact-registration.d.ts +6 -1
- package/dist/execution/artifact-registration.js +12 -4
- package/dist/execution/artifact-registry.d.ts +10 -3
- package/dist/execution/noop-artifact-registry.js +3 -4
- package/dist/execution/post-step-pipeline.d.ts +14 -3
- package/dist/execution/post-step-pipeline.js +21 -8
- package/dist/execution/run-authorizer.d.ts +5 -3
- package/dist/execution/run-canceler.js +4 -20
- package/dist/index.d.ts +13 -7
- package/dist/index.js +12 -6
- package/dist/lib/hooks.d.ts +49 -0
- package/dist/lib/hooks.js +39 -0
- package/dist/lib/result.d.ts +22 -11
- package/dist/lib/result.js +24 -14
- package/dist/loop/run-agent.d.ts +2 -2
- package/dist/loop/run-agent.js +5 -4
- package/dist/provenance/seam.d.ts +1 -1
- package/dist/providers/ai-sdk.d.ts +43 -5
- package/dist/providers/ai-sdk.js +55 -52
- package/dist/providers/anthropic.d.ts +2 -3
- package/dist/providers/anthropic.js +2 -2
- package/dist/providers/embedding.d.ts +13 -7
- package/dist/providers/embedding.js +26 -6
- package/dist/providers/errors.d.ts +67 -37
- package/dist/providers/errors.js +57 -58
- package/dist/providers/request-headers.d.ts +18 -0
- package/dist/providers/request-headers.js +22 -0
- package/dist/report-model/production-resolver.d.ts +5 -2
- package/dist/report-model/production-resolver.js +12 -2
- package/dist/runtime/assemble.d.ts +3 -2
- package/dist/runtime/dbos.d.ts +4 -3
- package/dist/runtime/dbos.js +18 -3
- package/dist/runtime/result-serialization.d.ts +29 -0
- package/dist/runtime/result-serialization.js +45 -0
- package/dist/sandbox/client.d.ts +11 -3
- package/dist/sandbox/create-sandbox.d.ts +5 -2
- package/dist/sandbox/create-sandbox.js +51 -25
- package/dist/sandbox/docker-client.d.ts +4 -3
- package/dist/sandbox/docker-client.js +23 -31
- package/dist/sandbox/k8s-client.d.ts +4 -13
- package/dist/sandbox/k8s-client.js +28 -61
- package/dist/sandbox/labels.d.ts +34 -0
- package/dist/sandbox/labels.js +47 -0
- package/dist/sandbox/mount-plan.d.ts +3 -1
- package/dist/sandbox/mount-plan.js +9 -0
- package/dist/sandbox/sandbox-error.d.ts +36 -13
- package/dist/sandbox/sandbox-error.js +32 -17
- package/dist/sandbox/types.d.ts +13 -9
- package/dist/schemas/validate-plan.js +13 -0
- package/dist/schemas/workflow-state.d.ts +4 -4
- package/dist/state/analyses.d.ts +4 -4
- package/dist/state/analyses.js +4 -4
- package/dist/state/schema.d.ts +5 -5
- package/dist/tasks/data-profile.d.ts +5 -8
- package/dist/tasks/data-profile.js +88 -43
- package/dist/tasks/derive-table-exec.d.ts +10 -6
- package/dist/tasks/derive-table-exec.js +17 -12
- package/dist/tasks/extract-values.d.ts +16 -10
- package/dist/tasks/extract-values.js +34 -18
- package/dist/tools/execute-analysis.js +22 -15
- package/dist/tools/report-session/derive-table.d.ts +9 -1
- package/dist/tools/report-session/derive-table.js +25 -22
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/workflows/execute-analysis.d.ts +25 -16
- package/dist/workflows/execute-analysis.js +223 -216
- package/dist/workflows/metrics.d.ts +4 -3
- package/dist/workflows/metrics.js +4 -3
- package/dist/workflows/sandbox-step.d.ts +20 -29
- package/dist/workflows/sandbox-step.js +71 -127
- package/dist/workflows/suspension.d.ts +32 -0
- package/dist/workflows/suspension.js +38 -0
- package/package.json +1 -1
- package/dist/billing/noop-resolver.d.ts +0 -6
- package/dist/billing/noop-resolver.js +0 -7
- package/dist/billing/resolver.d.ts +0 -64
- package/dist/billing/resolver.js +0 -24
- package/dist/loop/budget-exceeded.d.ts +0 -38
- package/dist/loop/budget-exceeded.js +0 -88
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* terminal frame read-side.
|
|
19
19
|
*/
|
|
20
20
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
21
|
+
import { deliverNotice } from "../lib/hooks.js";
|
|
21
22
|
import { elapsedSinceIso, recordRunCompleted, recordStepCompleted } from "../lib/metrics.js";
|
|
22
23
|
import { unwrapOrThrow } from "../lib/result.js";
|
|
23
24
|
import { markRunCanceledIfActive, queryRun } from "../state/runs.js";
|
|
@@ -113,26 +114,9 @@ export function createRunCanceler(deps) {
|
|
|
113
114
|
catch (err) {
|
|
114
115
|
logger.error("pending-step sweep failed", { runId, ...logger.errorFields(err) });
|
|
115
116
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
await runCharge.close({ analysisId: row.analysisId, runId, reason: "canceled", session });
|
|
121
|
-
charge = true;
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
logger.error("running-charge close failed", { runId, ...logger.errorFields(err) });
|
|
125
|
-
}
|
|
126
|
-
let mandate = row.mandateJti === null;
|
|
127
|
-
if (row.mandateJti !== null) {
|
|
128
|
-
try {
|
|
129
|
-
await runAuthorizer.revokeByJti({ jti: row.mandateJti, auth: session.auth }, EXTERNAL_CANCEL_REASON);
|
|
130
|
-
mandate = true;
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
logger.error("mandate revoke failed", { runId, jti: row.mandateJti, ...logger.errorFields(err) });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
117
|
+
const charge = await deliverNotice(logger.with({ runId }), "RunCharge.close", runCharge.close({ analysisId: row.analysisId, runId, outcome: { kind: "canceled" }, session }));
|
|
118
|
+
const mandate = row.mandateJti === null ||
|
|
119
|
+
(await deliverNotice(logger.with({ runId, jti: row.mandateJti }), "RunAuthorizer.revokeByJti", runAuthorizer.revokeByJti({ jti: row.mandateJti, auth: session.auth }, EXTERNAL_CANCEL_REASON)));
|
|
136
120
|
// No transition means the run reached a terminal state on its own (or the
|
|
137
121
|
// write failed) — report the row as it stands rather than claiming `canceled`.
|
|
138
122
|
let finalStatus = "canceled";
|
package/dist/index.d.ts
CHANGED
|
@@ -24,10 +24,10 @@ export type { ConversationAgentDeps } from "./agents/conversation-agent.js";
|
|
|
24
24
|
export type { BioToolKeys } from "./tools/bio/keys.js";
|
|
25
25
|
export { createLocalRunAuthorizer } from "./auth/local-run-authorizer.js";
|
|
26
26
|
export type { RunAuthorizer, AuthorizeRunInput, RunAuthorization, RevokeByJtiRef } from "./execution/run-authorizer.js";
|
|
27
|
-
export {
|
|
28
|
-
export type {
|
|
27
|
+
export type { GateFailure, NoticeFailure } from "./lib/hooks.js";
|
|
28
|
+
export type { RequestHeaders, ResolveRequestHeaders } from "./providers/request-headers.js";
|
|
29
29
|
export { createNoopRunCharge } from "./billing/noop-run-charge.js";
|
|
30
|
-
export type { RunCharge } from "./billing/run-charge.js";
|
|
30
|
+
export type { RunCharge, RunChargeOutcome } from "./billing/run-charge.js";
|
|
31
31
|
export { createNoopUsageRecorder } from "./billing/noop-usage-recorder.js";
|
|
32
32
|
export type { UsageRecorder, LlmUsageRecord } from "./billing/usage-recorder.js";
|
|
33
33
|
export { createNoopArtifactRegistry } from "./execution/noop-artifact-registry.js";
|
|
@@ -63,12 +63,12 @@ export type { RunAgentOptions, RunAgentResult, AgentFinish } from "./loop/run-ag
|
|
|
63
63
|
export type { AgentDefinition, RunStep, EmitFn, EmitEvent, EventSource } from "./loop/types.js";
|
|
64
64
|
export { passthroughStep } from "./loop/run-step.js";
|
|
65
65
|
export { forStep, forSubAgent, scopeResource, scopeWorkloadId } from "./auth/types.js";
|
|
66
|
-
export type { AgentSession, RequestSession, RunSession, Identity, Scope, Credential, Provenance, RunFrame, AuthContext, ResourceCoordinates, } from "./auth/types.js";
|
|
66
|
+
export type { AgentSession, RequestSession, RunSession, Identity, Scope, Credential, Provenance, RunFrame, AuthContext, ResourceCoordinates, HookSessionView, ResolvableSession, } from "./auth/types.js";
|
|
67
67
|
export { makeLocalAuth } from "./auth/local-auth-context.js";
|
|
68
68
|
export { createAnthropicProvider } from "./providers/anthropic.js";
|
|
69
69
|
export type { AnthropicProviderDeps } from "./providers/anthropic.js";
|
|
70
70
|
export { createConfiguredAiSdkProvider, DEFAULT_MAX_OUTPUT_TOKENS } from "./providers/ai-sdk.js";
|
|
71
|
-
export type { AiSdkProviderConfig, ConfiguredAiSdkProviderDeps } from "./providers/ai-sdk.js";
|
|
71
|
+
export type { AiSdkProviderConfig, ConfiguredAiSdkProviderDeps, ProviderHostPolicy } from "./providers/ai-sdk.js";
|
|
72
72
|
export { createEmbeddingProvider } from "./providers/embedding.js";
|
|
73
73
|
export type { EmbeddingProviderDeps } from "./providers/embedding.js";
|
|
74
74
|
export type { AgentChat, ChatProvider, EmbeddingProvider, ChatRequest, ChatResponse, ChatStreamEvent, ChatUsage, ModelMessage, PromptCachePolicy, ReasoningPolicy, } from "./providers/types.js";
|
|
@@ -76,6 +76,10 @@ export { DEFAULT_PROMPT_CACHE, promptCacheProviderOptions, withPromptCacheBreakp
|
|
|
76
76
|
export { DEFAULT_REASONING } from "./providers/reasoning.js";
|
|
77
77
|
export { toProviderError, isProviderError } from "./providers/errors.js";
|
|
78
78
|
export type { ProviderError } from "./providers/errors.js";
|
|
79
|
+
export { DEFAULT_SUSPEND_ON } from "./providers/errors.js";
|
|
80
|
+
export type { SuspendOn } from "./providers/errors.js";
|
|
81
|
+
export { suspensionOfFailure } from "./workflows/suspension.js";
|
|
82
|
+
export type { Suspension } from "./workflows/suspension.js";
|
|
79
83
|
export { createStreamingChat } from "./providers/streaming-chat.js";
|
|
80
84
|
export { prepareChatTurn } from "./app/chat-turn.js";
|
|
81
85
|
export type { PrepareChatTurnDeps, PrepareChatTurnParams, PrepareChatTurnResult } from "./app/chat-turn.js";
|
|
@@ -137,8 +141,10 @@ export type { ResolveWorkspaceRoot } from "./workspace/paths.js";
|
|
|
137
141
|
export { createSandboxClient } from "./sandbox/create-sandbox.js";
|
|
138
142
|
export type { CreateSandboxClientConfig, SandboxBackendConfig } from "./sandbox/create-sandbox.js";
|
|
139
143
|
export type { SandboxClient } from "./sandbox/client.js";
|
|
140
|
-
export {
|
|
141
|
-
export type {
|
|
144
|
+
export type { ResolveSandboxLabels, SandboxLabels, SandboxSpec } from "./sandbox/types.js";
|
|
145
|
+
export type { SpawnSession } from "./auth/types.js";
|
|
146
|
+
export { describeSandboxError, keepSuspendingRefusal, SandboxFailure } from "./sandbox/sandbox-error.js";
|
|
147
|
+
export type { SandboxError, SuspendingRefusal } from "./sandbox/sandbox-error.js";
|
|
142
148
|
export type { ExtendAnalysisFarm, FarmLocation, FarmResolution, FarmSource, PackageRequestOutcome, ResolveAnalysisFarm, ToolchainSource, } from "./sandbox/types.js";
|
|
143
149
|
export { formatQuery, identityAddress, identityKey, identityOf, parseIdentityKey, parseQuery, pythonIdentity, resolveQuery, rIdentity, } from "./sandbox/package-identity.js";
|
|
144
150
|
export type { PackageIdentity, PackageQuery, ParseQueryError, PoolIndex, QueryResolution, Track } from "./sandbox/package-identity.js";
|
package/dist/index.js
CHANGED
|
@@ -29,13 +29,11 @@ export { bootHarness } from "./runtime/boot.js";
|
|
|
29
29
|
export { createConversationAgent } from "./agents/conversation-agent.js";
|
|
30
30
|
// Seam: run authorization.
|
|
31
31
|
export { createLocalRunAuthorizer } from "./auth/local-run-authorizer.js";
|
|
32
|
-
// Seam: billing resolution.
|
|
33
|
-
export { createNoopBillingResolver } from "./billing/noop-resolver.js";
|
|
34
32
|
// Seam: run-level billing bracket.
|
|
35
33
|
export { createNoopRunCharge } from "./billing/noop-run-charge.js";
|
|
36
|
-
// Seam: per-call LLM usage accounting. `record` is
|
|
37
|
-
//
|
|
38
|
-
//
|
|
34
|
+
// Seam: per-call LLM usage accounting. `record` is a notice: the loop does not
|
|
35
|
+
// wait for its result, and it logs the reason of an err when the result
|
|
36
|
+
// arrives. Wired as the optional `usageRecorder` of `assembleCoreRuntime`;
|
|
39
37
|
// unwired, every call is dropped by `createNoopUsageRecorder`.
|
|
40
38
|
export { createNoopUsageRecorder } from "./billing/noop-usage-recorder.js";
|
|
41
39
|
// Seam: artifact registration.
|
|
@@ -135,6 +133,12 @@ export { DEFAULT_REASONING } from "./providers/reasoning.js";
|
|
|
135
133
|
// directly needs: `chatStream` throws a `ProviderError` value (a plain object,
|
|
136
134
|
// not an `Error`), so a `catch` recognizes it by shape, not `instanceof`.
|
|
137
135
|
export { toProviderError, isProviderError } from "./providers/errors.js";
|
|
136
|
+
// The suspend map of a provider: an HTTP status to the suspend reason of the
|
|
137
|
+
// host. A map from the host replaces `DEFAULT_SUSPEND_ON`, which holds `402`.
|
|
138
|
+
export { DEFAULT_SUSPEND_ON } from "./providers/errors.js";
|
|
139
|
+
// The suspension of work. A host reads the suspend reason of a failed chat turn
|
|
140
|
+
// with `suspensionOfFailure`, by the kind of the error, never by its text.
|
|
141
|
+
export { suspensionOfFailure } from "./workflows/suspension.js";
|
|
138
142
|
// `createStreamingChat` wraps a `ChatProvider` as a streaming `AgentChat` (the
|
|
139
143
|
// type `runAgent`'s `provider` option takes): its `chat` drives the provider's
|
|
140
144
|
// `chatStream` and forwards each text delta to `onText`, so a same-process host
|
|
@@ -248,7 +252,9 @@ export { computeInputSignature } from "./execution/input-signature.js";
|
|
|
248
252
|
export { createSandboxClient } from "./sandbox/create-sandbox.js";
|
|
249
253
|
// The throw of the client seam. An embedder matches the class and reads the
|
|
250
254
|
// typed variant on `error`, instead of a string match on the message.
|
|
251
|
-
|
|
255
|
+
// `createSandbox` gives each failure as an `err`, and `keepSuspendingRefusal`
|
|
256
|
+
// keeps a refusal of the label hook with the suspend flag as a value and throws each other failure.
|
|
257
|
+
export { describeSandboxError, keepSuspendingRefusal, SandboxFailure } from "./sandbox/sandbox-error.js";
|
|
252
258
|
// The identity of a package, the query that asks for one, and the resolution
|
|
253
259
|
// between the two. An embedder reads `by_name` of the dependency graph with
|
|
254
260
|
// these, thus the host lookup, the census, and the graph key never disagree on
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two kinds of host hook: a gate and a notice.
|
|
3
|
+
*
|
|
4
|
+
* A gate gives a value or a permission that the harness must have before it
|
|
5
|
+
* can continue an operation. A notice reports a fact after the operation. The
|
|
6
|
+
* type of each hook shows its kind:
|
|
7
|
+
*
|
|
8
|
+
* gate: (input) => ResultAsync<T, GateFailure>
|
|
9
|
+
* notice: (input) => ResultAsync<void, NoticeFailure>
|
|
10
|
+
*
|
|
11
|
+
* The harness calls each hook through `passGate` or `deliverNotice`, thus no
|
|
12
|
+
* call site selects its own failure policy, and no call site puts a `try` or a
|
|
13
|
+
* `catch` around a hook. A hook gives a failure as an `err`. A hook whose
|
|
14
|
+
* promise rejects has a defect, and the rejection passes through the helpers
|
|
15
|
+
* with no change.
|
|
16
|
+
*
|
|
17
|
+
* The reason of a failure is the text of the host. The harness carries it and
|
|
18
|
+
* never reads it. The flag `suspend` is part of the contract, thus the harness
|
|
19
|
+
* branches on it.
|
|
20
|
+
*/
|
|
21
|
+
import { ResultAsync } from "neverthrow";
|
|
22
|
+
import type { Logger } from "./logger.js";
|
|
23
|
+
export interface GateFailure {
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
/** True when the host asks the harness to suspend the work in place of a failure. */
|
|
26
|
+
readonly suspend: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface NoticeFailure {
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
}
|
|
31
|
+
export type GateName = "RunAuthorizer.authorize" | "RunCharge.open" | "ArtifactRegistry.register" | "resolveRequestHeaders" | "resolveSandboxLabels";
|
|
32
|
+
export type NoticeName = "RunAuthorizer.revoke" | "RunAuthorizer.revokeByJti" | "RunCharge.close" | "ArtifactRegistry.sync" | "UsageRecorder.record";
|
|
33
|
+
export type GateRefusal = {
|
|
34
|
+
readonly kind: "failed";
|
|
35
|
+
readonly gate: GateName;
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: "suspended";
|
|
39
|
+
readonly gate: GateName;
|
|
40
|
+
readonly reason: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The host can build its `Result` with its own copy of neverthrow. The helper
|
|
44
|
+
* gives the outcome again as a `Result` of the copy of the harness. Thus the
|
|
45
|
+
* checkpoint recipes (`runtime/result-serialization.ts`) recognize the class,
|
|
46
|
+
* and a replay reads the same outcome.
|
|
47
|
+
*/
|
|
48
|
+
export declare function passGate<T>(gate: GateName, result: ResultAsync<T, GateFailure>): ResultAsync<T, GateRefusal>;
|
|
49
|
+
export declare function deliverNotice(log: Logger, notice: NoticeName, result: ResultAsync<void, NoticeFailure>): Promise<boolean>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two kinds of host hook: a gate and a notice.
|
|
3
|
+
*
|
|
4
|
+
* A gate gives a value or a permission that the harness must have before it
|
|
5
|
+
* can continue an operation. A notice reports a fact after the operation. The
|
|
6
|
+
* type of each hook shows its kind:
|
|
7
|
+
*
|
|
8
|
+
* gate: (input) => ResultAsync<T, GateFailure>
|
|
9
|
+
* notice: (input) => ResultAsync<void, NoticeFailure>
|
|
10
|
+
*
|
|
11
|
+
* The harness calls each hook through `passGate` or `deliverNotice`, thus no
|
|
12
|
+
* call site selects its own failure policy, and no call site puts a `try` or a
|
|
13
|
+
* `catch` around a hook. A hook gives a failure as an `err`. A hook whose
|
|
14
|
+
* promise rejects has a defect, and the rejection passes through the helpers
|
|
15
|
+
* with no change.
|
|
16
|
+
*
|
|
17
|
+
* The reason of a failure is the text of the host. The harness carries it and
|
|
18
|
+
* never reads it. The flag `suspend` is part of the contract, thus the harness
|
|
19
|
+
* branches on it.
|
|
20
|
+
*/
|
|
21
|
+
import { err, ok, ResultAsync } from "neverthrow";
|
|
22
|
+
/**
|
|
23
|
+
* The host can build its `Result` with its own copy of neverthrow. The helper
|
|
24
|
+
* gives the outcome again as a `Result` of the copy of the harness. Thus the
|
|
25
|
+
* checkpoint recipes (`runtime/result-serialization.ts`) recognize the class,
|
|
26
|
+
* and a replay reads the same outcome.
|
|
27
|
+
*/
|
|
28
|
+
export function passGate(gate, result) {
|
|
29
|
+
return new ResultAsync(Promise.resolve(result).then((settled) => settled.isOk()
|
|
30
|
+
? ok(settled.value)
|
|
31
|
+
: err({ kind: settled.error.suspend ? "suspended" : "failed", gate, reason: settled.error.reason })));
|
|
32
|
+
}
|
|
33
|
+
export async function deliverNotice(log, notice, result) {
|
|
34
|
+
const delivered = await result;
|
|
35
|
+
if (delivered.isOk())
|
|
36
|
+
return true;
|
|
37
|
+
log.error("a notice of the host failed", { notice, reason: delivered.error.reason });
|
|
38
|
+
return false;
|
|
39
|
+
}
|
package/dist/lib/result.d.ts
CHANGED
|
@@ -4,20 +4,31 @@
|
|
|
4
4
|
* The agent loop and execution boundary model failure as values, not throws.
|
|
5
5
|
* The house rules:
|
|
6
6
|
*
|
|
7
|
-
* 1. `try/catch` lives ONLY
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 1. `try/catch` lives ONLY at a boundary:
|
|
8
|
+
* - the thin wrappers at calls to code external to ours — a third-party
|
|
9
|
+
* SDK, the `pg` driver, `fs`, the DBOS runtime. Those wrappers turn a
|
|
10
|
+
* throw into an `err(DomainError)`.
|
|
11
|
+
* - an API entry point, where a caller outside the harness gets the
|
|
12
|
+
* outcome.
|
|
13
|
+
* - a workflow body that gets the throw of a failed step or a failed
|
|
14
|
+
* child workflow, and runs its failure path.
|
|
15
|
+
* - the dispatch catch of the loop.
|
|
16
|
+
* Everything else flows `Result` / `ResultAsync`. A host hook has no
|
|
17
|
+
* `try/catch` around it: `passGate` / `deliverNotice` (`lib/hooks.ts`)
|
|
18
|
+
* consume its `Result`.
|
|
11
19
|
* 2. Absence is NOT an error. A "not found" stays in the ok channel as a
|
|
12
20
|
* data variant (`ok({ found: false })`), never an `err`. `err` is
|
|
13
21
|
* reserved for what used to be `throw`n — an unexpected upstream failure.
|
|
14
22
|
* 3. A `Result` is unwrapped at exactly three edges, each speaking its own
|
|
15
23
|
* dialect of failure:
|
|
16
24
|
* - HTTP routes `.match(...)` a `Result` into a response.
|
|
17
|
-
* - The DBOS step boundary throws on `err`
|
|
18
|
-
* `resultStep` in `loop/run-step.ts`):
|
|
19
|
-
* failed — and retries / fails fast —
|
|
20
|
-
*
|
|
25
|
+
* - The DBOS step boundary throws on an `err` that must fail or retry
|
|
26
|
+
* the step (`unwrapOrThrow` / `resultStep` in `loop/run-step.ts`):
|
|
27
|
+
* durability records a step as failed — and retries / fails fast —
|
|
28
|
+
* ONLY on a thrown exception. A step can also return a `Result` as
|
|
29
|
+
* its value: `runtime/result-serialization.ts` keeps the `Ok` / `Err`
|
|
30
|
+
* class across the checkpoint, DBOS records the step as a success,
|
|
31
|
+
* and the body MUST use the `err`.
|
|
21
32
|
* - Tool dispatch maps `err` into an `is_error` `tool_result`
|
|
22
33
|
* (`loop/run-agent.ts`).
|
|
23
34
|
*
|
|
@@ -43,9 +54,9 @@ export interface DomainError {
|
|
|
43
54
|
/**
|
|
44
55
|
* A thrown wrapper for a structured error value that is not already an
|
|
45
56
|
* `Error`. The original value rides on `.cause`, so the cause-walking
|
|
46
|
-
* classifiers (`classifyProviderError`, `
|
|
47
|
-
* `status` / `code` signals they look for after a `Result` is
|
|
48
|
-
* boundary.
|
|
57
|
+
* classifiers (`classifyProviderError`, `findSuspendError`) still reach the
|
|
58
|
+
* `status` / `code` / `type` signals they look for after a `Result` is
|
|
59
|
+
* rethrown at a boundary.
|
|
49
60
|
*/
|
|
50
61
|
export declare class ResultError extends Error {
|
|
51
62
|
readonly value: unknown;
|
package/dist/lib/result.js
CHANGED
|
@@ -4,20 +4,31 @@
|
|
|
4
4
|
* The agent loop and execution boundary model failure as values, not throws.
|
|
5
5
|
* The house rules:
|
|
6
6
|
*
|
|
7
|
-
* 1. `try/catch` lives ONLY
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 1. `try/catch` lives ONLY at a boundary:
|
|
8
|
+
* - the thin wrappers at calls to code external to ours — a third-party
|
|
9
|
+
* SDK, the `pg` driver, `fs`, the DBOS runtime. Those wrappers turn a
|
|
10
|
+
* throw into an `err(DomainError)`.
|
|
11
|
+
* - an API entry point, where a caller outside the harness gets the
|
|
12
|
+
* outcome.
|
|
13
|
+
* - a workflow body that gets the throw of a failed step or a failed
|
|
14
|
+
* child workflow, and runs its failure path.
|
|
15
|
+
* - the dispatch catch of the loop.
|
|
16
|
+
* Everything else flows `Result` / `ResultAsync`. A host hook has no
|
|
17
|
+
* `try/catch` around it: `passGate` / `deliverNotice` (`lib/hooks.ts`)
|
|
18
|
+
* consume its `Result`.
|
|
11
19
|
* 2. Absence is NOT an error. A "not found" stays in the ok channel as a
|
|
12
20
|
* data variant (`ok({ found: false })`), never an `err`. `err` is
|
|
13
21
|
* reserved for what used to be `throw`n — an unexpected upstream failure.
|
|
14
22
|
* 3. A `Result` is unwrapped at exactly three edges, each speaking its own
|
|
15
23
|
* dialect of failure:
|
|
16
24
|
* - HTTP routes `.match(...)` a `Result` into a response.
|
|
17
|
-
* - The DBOS step boundary throws on `err`
|
|
18
|
-
* `resultStep` in `loop/run-step.ts`):
|
|
19
|
-
* failed — and retries / fails fast —
|
|
20
|
-
*
|
|
25
|
+
* - The DBOS step boundary throws on an `err` that must fail or retry
|
|
26
|
+
* the step (`unwrapOrThrow` / `resultStep` in `loop/run-step.ts`):
|
|
27
|
+
* durability records a step as failed — and retries / fails fast —
|
|
28
|
+
* ONLY on a thrown exception. A step can also return a `Result` as
|
|
29
|
+
* its value: `runtime/result-serialization.ts` keeps the `Ok` / `Err`
|
|
30
|
+
* class across the checkpoint, DBOS records the step as a success,
|
|
31
|
+
* and the body MUST use the `err`.
|
|
21
32
|
* - Tool dispatch maps `err` into an `is_error` `tool_result`
|
|
22
33
|
* (`loop/run-agent.ts`).
|
|
23
34
|
*
|
|
@@ -32,9 +43,9 @@ import { Err, Ok } from "neverthrow";
|
|
|
32
43
|
/**
|
|
33
44
|
* A thrown wrapper for a structured error value that is not already an
|
|
34
45
|
* `Error`. The original value rides on `.cause`, so the cause-walking
|
|
35
|
-
* classifiers (`classifyProviderError`, `
|
|
36
|
-
* `status` / `code` signals they look for after a `Result` is
|
|
37
|
-
* boundary.
|
|
46
|
+
* classifiers (`classifyProviderError`, `findSuspendError`) still reach the
|
|
47
|
+
* `status` / `code` / `type` signals they look for after a `Result` is
|
|
48
|
+
* rethrown at a boundary.
|
|
38
49
|
*/
|
|
39
50
|
export class ResultError extends Error {
|
|
40
51
|
value;
|
|
@@ -49,9 +60,8 @@ function describe(value) {
|
|
|
49
60
|
return value.message;
|
|
50
61
|
if (value && typeof value === "object" && "type" in value) {
|
|
51
62
|
// Prefer a human `message` field when the structured error carries one
|
|
52
|
-
// (e.g. a `ProviderError`) so the wrapped `Error.message`
|
|
53
|
-
//
|
|
54
|
-
// to the `type` discriminant otherwise.
|
|
63
|
+
// (e.g. a `ProviderError`) so the wrapped `Error.message` names the
|
|
64
|
+
// failure; fall back to the `type` discriminant otherwise.
|
|
55
65
|
const message = value.message;
|
|
56
66
|
if (typeof message === "string" && message.length > 0)
|
|
57
67
|
return message;
|
package/dist/loop/run-agent.d.ts
CHANGED
|
@@ -100,8 +100,8 @@ export interface RunAgentOptions {
|
|
|
100
100
|
readonly logger?: Logger;
|
|
101
101
|
/**
|
|
102
102
|
* Per-call LLM usage-accounting seam. Omitted falls back to the no-op
|
|
103
|
-
* recorder.
|
|
104
|
-
*
|
|
103
|
+
* recorder. `record` is a notice: the loop does not wait for its result,
|
|
104
|
+
* and it logs the reason of an `err` when the result arrives.
|
|
105
105
|
*/
|
|
106
106
|
readonly usageRecorder?: UsageRecorder;
|
|
107
107
|
/**
|
package/dist/loop/run-agent.js
CHANGED
|
@@ -3,6 +3,7 @@ import { jsonSchema, tool as aiTool, } from "ai";
|
|
|
3
3
|
import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
|
|
4
4
|
import { stripNulCharacters } from "../input-sanitization.js";
|
|
5
5
|
import { createNoopLogger } from "../lib/console-logger.js";
|
|
6
|
+
import { deliverNotice } from "../lib/hooks.js";
|
|
6
7
|
import { ATTR_INFLEXA_TOOL_USE_ID, passThroughSpan, stableSpan } from "../lib/otel-spans.js";
|
|
7
8
|
import { ResultError } from "../lib/result.js";
|
|
8
9
|
import { describeZodIssueShapes } from "../lib/zod-issue-shape.js";
|
|
@@ -150,8 +151,8 @@ async function runAgentLoop(agent, initial, session, opts) {
|
|
|
150
151
|
* reported nothing to account for, so it is folded (to no effect) and left
|
|
151
152
|
* out of the ledger rather than entered as an all-absent record.
|
|
152
153
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
154
|
+
* `record` is a notice. The loop does not wait for its result, thus a
|
|
155
|
+
* recorder that blocks does not make the run slower.
|
|
155
156
|
*/
|
|
156
157
|
const accountForCall = (reply, stepName) => {
|
|
157
158
|
addChatUsage(usage, reply.usage);
|
|
@@ -159,7 +160,7 @@ async function runAgentLoop(agent, initial, session, opts) {
|
|
|
159
160
|
const reported = reply.usage;
|
|
160
161
|
if (reported === undefined || !hasReportedUsage(reported))
|
|
161
162
|
return;
|
|
162
|
-
usageRecorder.record({
|
|
163
|
+
void deliverNotice(log, "UsageRecorder.record", usageRecorder.record({
|
|
163
164
|
recordKey: recordKeyFor(session, opts.invocationId, stepName),
|
|
164
165
|
agentId: source.agentId,
|
|
165
166
|
callPath: source.callPath,
|
|
@@ -169,7 +170,7 @@ async function runAgentLoop(agent, initial, session, opts) {
|
|
|
169
170
|
...(reply.requestedModelId === undefined ? {} : { requestedModelId: reply.requestedModelId }),
|
|
170
171
|
...(reply.servedModelId === undefined ? {} : { servedModelId: reply.servedModelId }),
|
|
171
172
|
usage: reported,
|
|
172
|
-
});
|
|
173
|
+
}));
|
|
173
174
|
};
|
|
174
175
|
/** The rollups this loop stamps on its finish — each absent when nothing reported. */
|
|
175
176
|
const finishUsage = () => ({
|
|
@@ -69,7 +69,7 @@ export type RunProvenanceEvent = {
|
|
|
69
69
|
runId: string;
|
|
70
70
|
/**
|
|
71
71
|
* The body's terminal status. Both boundary sites resolve it through `deriveFinalStatus`,
|
|
72
|
-
* which records a
|
|
72
|
+
* which records a suspension as `"canceled"` — so `"suspended_insufficient_funds"` (a
|
|
73
73
|
* `RunStatus` member) is never emitted here and is deliberately absent from this narrower
|
|
74
74
|
* `ExecuteAnalysisFinalStatus`-minus-`"running"` set.
|
|
75
75
|
*/
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type LanguageModel } from "ai";
|
|
2
|
-
import type
|
|
2
|
+
import { type AgentSession } from "../auth/types.js";
|
|
3
3
|
import type { Logger } from "../lib/logger.js";
|
|
4
|
+
import { type ProviderError, type SuspendOn } from "./errors.js";
|
|
5
|
+
import { type ResolveRequestHeaders } from "./request-headers.js";
|
|
4
6
|
import type { ChatProvider, FetchLike, ProviderCapabilities } from "./types.js";
|
|
5
7
|
/**
|
|
6
8
|
* The harness-owned retry envelope. The AI SDK's built-in retry exposes only a
|
|
@@ -30,9 +32,13 @@ export declare const RETRY_MAX_DELAY_MS = 30000;
|
|
|
30
32
|
* and the server then enforces the true cap of the model.
|
|
31
33
|
*/
|
|
32
34
|
export declare const DEFAULT_MAX_OUTPUT_TOKENS = 64000;
|
|
33
|
-
|
|
35
|
+
/** With no `suspendOn`, a provider uses {@link DEFAULT_SUSPEND_ON}. */
|
|
36
|
+
export interface ProviderHostPolicy {
|
|
37
|
+
readonly resolveRequestHeaders?: ResolveRequestHeaders;
|
|
38
|
+
readonly suspendOn?: SuspendOn;
|
|
39
|
+
}
|
|
40
|
+
export interface AiSdkProviderDeps extends ProviderHostPolicy {
|
|
34
41
|
readonly model: LanguageModel;
|
|
35
|
-
readonly resolveBilling: ResolveBilling;
|
|
36
42
|
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
37
43
|
readonly logger?: Logger;
|
|
38
44
|
/**
|
|
@@ -193,9 +199,8 @@ export type AiSdkProviderConfig = {
|
|
|
193
199
|
*/
|
|
194
200
|
readonly maxRetries?: number;
|
|
195
201
|
};
|
|
196
|
-
export interface ConfiguredAiSdkProviderDeps {
|
|
202
|
+
export interface ConfiguredAiSdkProviderDeps extends ProviderHostPolicy {
|
|
197
203
|
readonly config: AiSdkProviderConfig;
|
|
198
|
-
readonly resolveBilling: ResolveBilling;
|
|
199
204
|
readonly logger?: Logger;
|
|
200
205
|
}
|
|
201
206
|
/**
|
|
@@ -207,6 +212,38 @@ export interface ConfiguredAiSdkProviderDeps {
|
|
|
207
212
|
* the same retry tick. `random` is injectable so the delay is testable.
|
|
208
213
|
*/
|
|
209
214
|
export declare function computeRetryDelayMs(error: unknown, exponentialBackoffDelay: number, random?: () => number): number;
|
|
215
|
+
/**
|
|
216
|
+
* A rejection of the request headers hook is a defect of the host, and the
|
|
217
|
+
* harness does not catch it (host-hooks spec): the envelope never retries it,
|
|
218
|
+
* and the outer catch rethrows it unchanged. The hook call has no `catch`, thus
|
|
219
|
+
* this flag marks the rejection: it stays set when the hook call of the attempt
|
|
220
|
+
* did not settle as a value.
|
|
221
|
+
*/
|
|
222
|
+
export interface HookCall {
|
|
223
|
+
unsettled: boolean;
|
|
224
|
+
}
|
|
225
|
+
/** A refusal of the hook throws `HeadersRefused`. */
|
|
226
|
+
export declare function headersForAttempt(hook: ResolveRequestHeaders | undefined, session: AgentSession, call: HookCall): Promise<Record<string, string>>;
|
|
227
|
+
export declare function failureOf(e: unknown, workload: string, suspendOn: SuspendOn): ProviderError;
|
|
228
|
+
/**
|
|
229
|
+
* Wrap a single wire attempt in the harness retry envelope. Built per call so
|
|
230
|
+
* the caller's `AbortSignal` reaches the primitive: it rethrows abort errors
|
|
231
|
+
* without retrying and cancels its own backoff sleep when the signal fires, so
|
|
232
|
+
* the retried closure adds no abort handling of its own. `shouldRetry` defers to
|
|
233
|
+
* the harness retryability taxonomy under the `suspendOn` map of the provider —
|
|
234
|
+
* thus a mapped status is never retried — and it never retries a refusal or a
|
|
235
|
+
* rejection of the request headers hook. A non-retried first failure is rethrown untouched,
|
|
236
|
+
* preserving the exact throwable the outer catch classifies. `createRetryError` carries the last real failure on `cause` so
|
|
237
|
+
* that, once retries are exhausted, `toProviderError`'s status walk reaches the
|
|
238
|
+
* true HTTP status instead of stopping at a synthetic wrapper. The attempt
|
|
239
|
+
* counter is per-instance because each call builds its own retry.
|
|
240
|
+
*
|
|
241
|
+
* An aborted caller signal also stops the retry. A wall-clock guard of a caller
|
|
242
|
+
* aborts with a `TimeoutError` reason, which the taxonomy marks as a retryable
|
|
243
|
+
* timeout. Without this guard the envelope would loop an expiry that the caller
|
|
244
|
+
* itself declared.
|
|
245
|
+
*/
|
|
246
|
+
export declare function createRetry(signal: AbortSignal | undefined, logger: Logger, maxRetries: number, suspendOn: SuspendOn, hookCall: HookCall): import("@ai-sdk/provider-utils").RetryFunction;
|
|
210
247
|
export declare function createAiSdkProvider(deps: AiSdkProviderDeps): ChatProvider;
|
|
211
248
|
/**
|
|
212
249
|
* Wrap a fetch so that the wait until the response starts does not exceed
|
|
@@ -227,6 +264,7 @@ export declare function createAiSdkProvider(deps: AiSdkProviderDeps): ChatProvid
|
|
|
227
264
|
* and the behavior stays byte-identical.
|
|
228
265
|
*/
|
|
229
266
|
export declare function wrapFetchWithRequestTimeout(fetchImpl: FetchLike, requestTimeoutMs: number): FetchLike;
|
|
267
|
+
export declare function hostPolicyOf(deps: ProviderHostPolicy): ProviderHostPolicy;
|
|
230
268
|
/**
|
|
231
269
|
* Realize an `AiSdkProviderConfig` into a `ChatProvider` bound to that config's
|
|
232
270
|
* connection and model. The model is closed into the returned provider here (it
|