bazis 0.99.1 → 0.99.3
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/core/agent/AgentToolExecutor.ts +81 -41
- package/core/agent/AgentToolHooks.ts +1 -1
- package/core/agent/README.md +15 -0
- package/core/agent/errors.ts +15 -2
- package/core/agent/index.ts +1 -0
- package/core/agent/internal/AgentExecutionDriver.ts +13 -1
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/infra/connectors/llm.ts +17 -2
- package/index.ts +1 -0
- package/package.json +1 -1
|
@@ -7,7 +7,8 @@ import { assertAgentToolHookIdV1, getAgentModuleContributionsV1, type AgentModul
|
|
|
7
7
|
import type { AgentToolBeforeEffectEventV1, AgentToolEnforcementDecisionV1, AgentToolHookContextV1, AgentToolRequiredHookRefV1, AgentToolAuditHookProjectionV1, AgentToolSettlementEventV1 } from "./AgentToolHooks";
|
|
8
8
|
import { agentModelJson } from "./internal/AgentModelBinding";
|
|
9
9
|
import { redactSensitive, type SensitiveRedactionOptions } from "../../library/redaction";
|
|
10
|
-
import { AgentSetupError, AgentToolExecutionError, AgentToolPreCommitError } from "./errors";
|
|
10
|
+
import { AgentSetupError, AgentToolExecutionError, AgentToolPreCommitError, ToolError } from "./errors";
|
|
11
|
+
import type { Logger } from "../kernel/logging/Logger";
|
|
11
12
|
import type { AgentRegistry, ToolDefinition } from "./AgentRegistry";
|
|
12
13
|
import type { JsonObject, JsonValue, AgentToolCall, AgentToolResult } from "./semantic";
|
|
13
14
|
import { agentToolCall, agentToolResult, normalizeJsonValue } from "./semantic";
|
|
@@ -326,14 +327,14 @@ function isSettlementResult(value: unknown): value is { readonly status: "record
|
|
|
326
327
|
} catch { return false; }
|
|
327
328
|
}
|
|
328
329
|
|
|
329
|
-
type HookOutcome<T> = { readonly kind: "value"; readonly value: T } | { readonly kind: "timeout" } | { readonly kind: "cancelled" } | { readonly kind: "failed" };
|
|
330
|
+
type HookOutcome<T> = { readonly kind: "value"; readonly value: T } | { readonly kind: "timeout" } | { readonly kind: "cancelled" } | { readonly kind: "failed"; readonly error?: unknown };
|
|
330
331
|
|
|
331
332
|
async function awaitHook<T>(operation: Promise<T>, signal: AbortSignal | undefined, timeoutMs: number | undefined): Promise<HookOutcome<T>> {
|
|
332
333
|
// Observe both branches even after the caller's deadline wins: a late hook
|
|
333
334
|
// rejection must never become an unhandled process-level rejection.
|
|
334
335
|
const observed: Promise<HookOutcome<T>> = operation
|
|
335
336
|
.then((value): HookOutcome<T> => ({ kind: "value", value }))
|
|
336
|
-
.catch((): HookOutcome<T> => ({ kind: "failed" }));
|
|
337
|
+
.catch((error: unknown): HookOutcome<T> => ({ kind: "failed", error }));
|
|
337
338
|
if (signal?.aborted) return { kind: "cancelled" };
|
|
338
339
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
339
340
|
let detach: (() => void) | undefined;
|
|
@@ -430,6 +431,8 @@ export class AgentToolExecutor {
|
|
|
430
431
|
private readonly services: ServiceProvider,
|
|
431
432
|
private readonly registry: AgentRegistry,
|
|
432
433
|
options: AgentToolExecutorOptions = {},
|
|
434
|
+
/** Where unexpected tool failures go with their stack; the model sees a generic message. */
|
|
435
|
+
private readonly logger?: Logger,
|
|
433
436
|
) {
|
|
434
437
|
assertNonNegativeInteger(options.defaultTimeoutMs, "AgentToolExecutor.defaultTimeoutMs");
|
|
435
438
|
assertPositiveInteger(options.scopeDisposeTimeoutMs, "AgentToolExecutor.scopeDisposeTimeoutMs");
|
|
@@ -643,6 +646,7 @@ export class AgentToolExecutor {
|
|
|
643
646
|
tier: this.isPlatformHook(record) ? "platform" : "application", order: registration.order ?? 0,
|
|
644
647
|
outcome: "failed",
|
|
645
648
|
};
|
|
649
|
+
let hookError: unknown;
|
|
646
650
|
const postEffect = state.executeStarted;
|
|
647
651
|
const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
|
|
648
652
|
const remainingMs = !postEffect && state.deadlineUnixMs !== undefined
|
|
@@ -680,6 +684,7 @@ export class AgentToolExecutor {
|
|
|
680
684
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, boundedTimeoutMs);
|
|
681
685
|
if (activated.kind !== "value") {
|
|
682
686
|
if (activated.kind === "timeout") controller.abort();
|
|
687
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
683
688
|
projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? (state.callerSignal?.aborted ? "cancelled" : "timed-out") : "failed" };
|
|
684
689
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement handler was unavailable.", metadata, startedAt);
|
|
685
690
|
} else {
|
|
@@ -692,6 +697,7 @@ export class AgentToolExecutor {
|
|
|
692
697
|
const outcome = await awaitHook(Promise.resolve((handler.settle as (value: AgentToolSettlementEventV1, context: AgentToolHookContextV1) => unknown)(event, context)), controller.signal, invocationTimeoutMs);
|
|
693
698
|
if (outcome.kind === "timeout") controller.abort();
|
|
694
699
|
if (outcome.kind !== "value") {
|
|
700
|
+
if (outcome.kind === "failed") hookError = outcome.error;
|
|
695
701
|
projection = { ...projection, outcome: outcome.kind === "timeout" ? "timed-out" : outcome.kind === "cancelled" ? (state.callerSignal?.aborted ? "cancelled" : "timed-out") : "failed" };
|
|
696
702
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement failed.", metadata, startedAt);
|
|
697
703
|
} else if (!isSettlementResult(outcome.value)) {
|
|
@@ -704,17 +710,20 @@ export class AgentToolExecutor {
|
|
|
704
710
|
}
|
|
705
711
|
}
|
|
706
712
|
}
|
|
707
|
-
} catch {
|
|
713
|
+
} catch (error) {
|
|
714
|
+
hookError = error;
|
|
708
715
|
projection = { ...projection, outcome: "failed" };
|
|
709
716
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement failed.", metadata, startedAt);
|
|
710
717
|
} finally {
|
|
711
718
|
detach?.();
|
|
712
719
|
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
713
720
|
if (disposeError !== undefined) {
|
|
721
|
+
hookError ??= disposeError;
|
|
714
722
|
projection = { ...projection, outcome: "failed" };
|
|
715
723
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement disposal failed.", metadata, startedAt);
|
|
716
724
|
}
|
|
717
725
|
}
|
|
726
|
+
this.logHookFailure(projection, call, event.agentName, hookError);
|
|
718
727
|
const evidenceRemainingMs = postEffect || state.deadlineUnixMs === undefined
|
|
719
728
|
? registration.timeoutMs ?? 5_000
|
|
720
729
|
: Math.max(0, Math.min(registration.timeoutMs ?? 5_000, state.deadlineUnixMs - Date.now()));
|
|
@@ -741,6 +750,7 @@ export class AgentToolExecutor {
|
|
|
741
750
|
if (registration.kind !== "observer") continue;
|
|
742
751
|
const scope = this.services.createScope();
|
|
743
752
|
let outcome: AgentToolAuditHookProjectionV1["outcome"] = "failed";
|
|
753
|
+
let hookError: unknown;
|
|
744
754
|
const timeoutMs = registration.timeoutMs ?? 5_000;
|
|
745
755
|
const controller = new AbortController();
|
|
746
756
|
const detach = attachAbortForwarding(options.signal, controller);
|
|
@@ -749,6 +759,7 @@ export class AgentToolExecutor {
|
|
|
749
759
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
|
|
750
760
|
if (activated.kind !== "value") {
|
|
751
761
|
if (activated.kind === "timeout") controller.abort();
|
|
762
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
752
763
|
outcome = activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed";
|
|
753
764
|
} else {
|
|
754
765
|
const handler = activated.value as { observe?: unknown };
|
|
@@ -761,15 +772,22 @@ export class AgentToolExecutor {
|
|
|
761
772
|
Math.max(1, context.deadlineUnixMs - Date.now()),
|
|
762
773
|
);
|
|
763
774
|
if (observed.kind === "timeout" || observed.kind === "value") controller.abort();
|
|
775
|
+
if (observed.kind === "failed") hookError = observed.error;
|
|
764
776
|
outcome = observed.kind === "value" ? "recorded" : observed.kind === "timeout" ? "timed-out" : observed.kind === "cancelled" ? "cancelled" : "failed";
|
|
765
777
|
}
|
|
766
778
|
}
|
|
767
|
-
} catch {
|
|
779
|
+
} catch (error) {
|
|
780
|
+
hookError = error;
|
|
768
781
|
outcome = controller.signal.aborted && options.signal?.aborted ? "cancelled" : "failed";
|
|
769
782
|
} finally {
|
|
770
783
|
detach?.();
|
|
771
|
-
|
|
784
|
+
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
785
|
+
if (disposeError !== undefined) {
|
|
786
|
+
hookError ??= disposeError;
|
|
787
|
+
outcome = "failed";
|
|
788
|
+
}
|
|
772
789
|
}
|
|
790
|
+
this.logHookFailure({ kind: registration.kind, id: registration.id, version: registration.version, outcome }, { id: settlement.callId, name: settlement.toolName }, settlement.agentName, hookError);
|
|
773
791
|
await this.recordObserverHook(settlement, registration, record, outcome, timeoutMs);
|
|
774
792
|
}
|
|
775
793
|
}
|
|
@@ -1083,6 +1101,7 @@ export class AgentToolExecutor {
|
|
|
1083
1101
|
tier: isPlatform(record) ? "platform" : "application", order: registration.order ?? 0,
|
|
1084
1102
|
outcome: "failed",
|
|
1085
1103
|
};
|
|
1104
|
+
let hookError: unknown;
|
|
1086
1105
|
const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
|
|
1087
1106
|
const remainingMs = executionState.deadlineUnixMs === undefined
|
|
1088
1107
|
? undefined : Math.max(0, executionState.deadlineUnixMs - Date.now());
|
|
@@ -1099,6 +1118,7 @@ export class AgentToolExecutor {
|
|
|
1099
1118
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
|
|
1100
1119
|
if (activated.kind !== "value") {
|
|
1101
1120
|
if (activated.kind === "timeout") controller.abort();
|
|
1121
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
1102
1122
|
projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed" };
|
|
1103
1123
|
terminal = this.errorResult(call, activated.kind === "timeout" ? "TOOL_HOOK_TIMEOUT" : activated.kind === "cancelled" ? "TOOL_HOOK_CANCELLED" : "TOOL_HOOK_UNAVAILABLE", "Agent Tool enforcement handler was unavailable.", metadata, startedAt);
|
|
1104
1124
|
} else {
|
|
@@ -1110,27 +1130,31 @@ export class AgentToolExecutor {
|
|
|
1110
1130
|
if (outcome.kind === "timeout") controller.abort();
|
|
1111
1131
|
if (outcome.kind === "timeout") { projection = { ...projection, outcome: "timed-out" }; terminal = this.errorResult(call, "TOOL_HOOK_TIMEOUT", "Agent Tool enforcement hook timed out.", metadata, startedAt); }
|
|
1112
1132
|
else if (outcome.kind === "cancelled") { projection = { ...projection, outcome: "cancelled" }; terminal = this.errorResult(call, "TOOL_HOOK_CANCELLED", "Agent Tool enforcement hook was cancelled.", metadata, startedAt); }
|
|
1113
|
-
else if (outcome.kind === "failed") { terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt); }
|
|
1133
|
+
else if (outcome.kind === "failed") { hookError = outcome.error; terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt); }
|
|
1114
1134
|
else if (!isHookDecision(outcome.value)) { projection = { ...projection, outcome: "invalid-result" }; terminal = this.errorResult(call, "TOOL_HOOK_DECISION_INVALID", "Agent Tool enforcement hook returned an invalid decision.", metadata, startedAt); }
|
|
1115
1135
|
else {
|
|
1116
1136
|
const decision = outcome.value;
|
|
1117
|
-
projection = { ...projection, outcome: decision.decision === "allow" ? "allowed" : "denied", ...(decision.decision === "deny" && decision.reasonCode ? { reasonCode: decision.reasonCode } : {}), ...(decision.evidence ? { evidence: redactSensitive(normalizeHookEvidence(decision.evidence), this.hookRedaction) as JsonObject } : {}) };
|
|
1118
|
-
|
|
1137
|
+
projection = { ...projection, outcome: decision.decision === "allow" ? "allowed" : "denied", ...(decision.decision === "deny" && decision.reasonCode ? { reasonCode: decision.reasonCode } : {}), ...(decision.decision === "deny" && decision.message ? { message: redactSensitive(decision.message) as string } : {}), ...(decision.evidence ? { evidence: redactSensitive(normalizeHookEvidence(decision.evidence), this.hookRedaction) as JsonObject } : {}) };
|
|
1138
|
+
// The hook's message is meant for the model, like a ToolError; reasonCode helps it branch.
|
|
1139
|
+
if (decision.decision === "deny") terminal = this.deniedResult(call, "TOOL_HOOK_DENIED", decision.message ?? "Tool execution was denied by an enforcement hook.", decision.reasonCode ? { reasonCode: decision.reasonCode } : {}, metadata, startedAt);
|
|
1119
1140
|
}
|
|
1120
1141
|
}
|
|
1121
1142
|
}
|
|
1122
1143
|
}
|
|
1123
|
-
} catch {
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
hookError = error;
|
|
1124
1146
|
projection = { ...projection, outcome: "failed" };
|
|
1125
1147
|
terminal = this.errorResult(call, options.signal?.aborted ? "TOOL_HOOK_CANCELLED" : "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt);
|
|
1126
1148
|
} finally {
|
|
1127
1149
|
detach?.();
|
|
1128
1150
|
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
1129
1151
|
if (disposeError !== undefined) {
|
|
1152
|
+
hookError ??= disposeError;
|
|
1130
1153
|
projection = { ...projection, outcome: "failed" };
|
|
1131
1154
|
terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook disposal failed.", metadata, startedAt);
|
|
1132
1155
|
}
|
|
1133
1156
|
}
|
|
1157
|
+
this.logHookFailure(projection, call, agentName, hookError);
|
|
1134
1158
|
const evidenceRemainingMs = executionState.deadlineUnixMs === undefined
|
|
1135
1159
|
? registration.timeoutMs ?? 5_000
|
|
1136
1160
|
: Math.max(0, Math.min(registration.timeoutMs ?? 5_000, executionState.deadlineUnixMs - Date.now()));
|
|
@@ -1530,15 +1554,12 @@ export class AgentToolExecutor {
|
|
|
1530
1554
|
);
|
|
1531
1555
|
});
|
|
1532
1556
|
const code = this.interruptionCode(error, tool, executionState);
|
|
1557
|
+
if (code.endsWith("_OUTCOME_UNKNOWN")) {
|
|
1558
|
+
this.logger?.error("Agent tool outcome unknown", { agent: agentName ?? null, tool: call.name, callId: call.id, code, phase: executionState.phase });
|
|
1559
|
+
}
|
|
1533
1560
|
return this.errorResult(call, code, executionState.timedOut ? "Tool execution timed out." : errorMessageOf(error), this.attemptMetadata(metadata, attempt), startedAt, { phase: executionState.phase });
|
|
1534
1561
|
}
|
|
1535
|
-
result = this.
|
|
1536
|
-
call,
|
|
1537
|
-
"TOOL_EXECUTION_FAILED",
|
|
1538
|
-
errorMessageOf(error),
|
|
1539
|
-
this.attemptMetadata(metadata, attempt),
|
|
1540
|
-
startedAt,
|
|
1541
|
-
);
|
|
1562
|
+
result = this.failedResult(call, agentName, "TOOL_EXECUTION_FAILED", error, "Tool execution failed.", this.attemptMetadata(metadata, attempt), startedAt);
|
|
1542
1563
|
} finally {
|
|
1543
1564
|
detach?.();
|
|
1544
1565
|
}
|
|
@@ -1582,13 +1603,7 @@ export class AgentToolExecutor {
|
|
|
1582
1603
|
? await contributed[0]!.activation.activateAsync(scope)
|
|
1583
1604
|
: await scope.resolveAsync(tool.target);
|
|
1584
1605
|
} catch (error) {
|
|
1585
|
-
return this.
|
|
1586
|
-
call,
|
|
1587
|
-
"TOOL_RESOLVE_FAILED",
|
|
1588
|
-
`Tool resolve failed: ${errorMessageOf(error)}`,
|
|
1589
|
-
this.attemptMetadata(metadata, attempt),
|
|
1590
|
-
startedAt,
|
|
1591
|
-
);
|
|
1606
|
+
return this.failedResult(call, agentName, "TOOL_RESOLVE_FAILED", error, "Tool could not be created.", this.attemptMetadata(metadata, attempt), startedAt);
|
|
1592
1607
|
}
|
|
1593
1608
|
|
|
1594
1609
|
if (signal.aborted) {
|
|
@@ -1638,12 +1653,14 @@ export class AgentToolExecutor {
|
|
|
1638
1653
|
const explicitlyPreCommit = error instanceof AgentToolPreCommitError;
|
|
1639
1654
|
const outcomeUnknown = !explicitlyPreCommit
|
|
1640
1655
|
&& (tool.metadata.sideEffect === "write" || tool.metadata.sideEffect === "external");
|
|
1641
|
-
return this.
|
|
1656
|
+
return this.failedResult(
|
|
1642
1657
|
call,
|
|
1658
|
+
agentName,
|
|
1643
1659
|
outcomeUnknown ? "TOOL_EXECUTION_FAILED_OUTCOME_UNKNOWN" : "TOOL_EXECUTION_FAILED",
|
|
1660
|
+
error,
|
|
1644
1661
|
outcomeUnknown
|
|
1645
|
-
? `Tool "${tool.metadata.name}" threw after side-effect execution started; retry safety is unknown
|
|
1646
|
-
:
|
|
1662
|
+
? `Tool "${tool.metadata.name}" threw after side-effect execution started; retry safety is unknown.`
|
|
1663
|
+
: "Tool execution failed.",
|
|
1647
1664
|
this.attemptMetadata(metadata, attempt),
|
|
1648
1665
|
startedAt,
|
|
1649
1666
|
outcomeUnknown ? { sideEffect: tool.metadata.sideEffect, phase: "execute" } : undefined,
|
|
@@ -1664,13 +1681,7 @@ export class AgentToolExecutor {
|
|
|
1664
1681
|
return this.postExecutionOutcome(
|
|
1665
1682
|
call,
|
|
1666
1683
|
tool,
|
|
1667
|
-
this.
|
|
1668
|
-
call,
|
|
1669
|
-
"TOOL_OUTPUT_INVALID",
|
|
1670
|
-
`Tool output processing failed: ${errorMessageOf(error)}`,
|
|
1671
|
-
this.attemptMetadata(metadata, attempt),
|
|
1672
|
-
startedAt,
|
|
1673
|
-
),
|
|
1684
|
+
this.failedResult(call, agentName, "TOOL_OUTPUT_INVALID", error, "Tool output processing failed.", this.attemptMetadata(metadata, attempt), startedAt),
|
|
1674
1685
|
metadata,
|
|
1675
1686
|
startedAt,
|
|
1676
1687
|
attempt,
|
|
@@ -1692,13 +1703,7 @@ export class AgentToolExecutor {
|
|
|
1692
1703
|
return this.postExecutionOutcome(
|
|
1693
1704
|
call,
|
|
1694
1705
|
tool,
|
|
1695
|
-
this.
|
|
1696
|
-
call,
|
|
1697
|
-
"TOOL_RESULT_INVALID",
|
|
1698
|
-
errorMessageOf(error),
|
|
1699
|
-
this.attemptMetadata(metadata, attempt),
|
|
1700
|
-
startedAt,
|
|
1701
|
-
),
|
|
1706
|
+
this.failedResult(call, agentName, "TOOL_RESULT_INVALID", error, "Tool result is invalid.", this.attemptMetadata(metadata, attempt), startedAt),
|
|
1702
1707
|
metadata,
|
|
1703
1708
|
startedAt,
|
|
1704
1709
|
attempt,
|
|
@@ -2056,6 +2061,41 @@ export class AgentToolExecutor {
|
|
|
2056
2061
|
}
|
|
2057
2062
|
|
|
2058
2063
|
|
|
2064
|
+
/** A hook that failed, timed out or answered nonsense; a cancelled run is not a failure. */
|
|
2065
|
+
private logHookFailure(
|
|
2066
|
+
hook: Pick<AgentToolAuditHookProjectionV1, "kind" | "id" | "version" | "outcome">,
|
|
2067
|
+
call: Pick<AgentToolCall, "id" | "name">,
|
|
2068
|
+
agentName: string | undefined,
|
|
2069
|
+
error: unknown,
|
|
2070
|
+
): void {
|
|
2071
|
+
if (hook.outcome !== "failed" && hook.outcome !== "timed-out" && hook.outcome !== "invalid-result") return;
|
|
2072
|
+
this.logger?.error("Agent tool hook failed", {
|
|
2073
|
+
hook: `${hook.kind}:${hook.id}@${hook.version}`, outcome: hook.outcome, agent: agentName ?? null, tool: call.name, callId: call.id,
|
|
2074
|
+
...(error === undefined ? {} : { error: redactSensitive(error) }),
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
/**
|
|
2079
|
+
* A tool's own failure: a ToolError message reaches the model as written;
|
|
2080
|
+
* anything else is replaced by `fallback` and logged with its stack.
|
|
2081
|
+
*/
|
|
2082
|
+
private failedResult(
|
|
2083
|
+
call: AgentToolCall,
|
|
2084
|
+
agentName: string | undefined,
|
|
2085
|
+
code: string,
|
|
2086
|
+
error: unknown,
|
|
2087
|
+
fallback: string,
|
|
2088
|
+
metadata: JsonObject,
|
|
2089
|
+
startedAt: number,
|
|
2090
|
+
details?: JsonObject,
|
|
2091
|
+
): AgentToolResult {
|
|
2092
|
+
if (error instanceof ToolError) {
|
|
2093
|
+
return this.errorResult(call, code, error.message, metadata, startedAt, details);
|
|
2094
|
+
}
|
|
2095
|
+
this.logger?.error("Agent tool failed", { agent: agentName ?? null, tool: call.name, callId: call.id, code, error: redactSensitive(error) });
|
|
2096
|
+
return this.errorResult(call, code, fallback, metadata, startedAt, details);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2059
2099
|
private errorResult(
|
|
2060
2100
|
call: AgentToolCall,
|
|
2061
2101
|
code: string,
|
|
@@ -36,4 +36,4 @@ export interface AgentToolSettlementResultV1 { readonly status: "recorded"; read
|
|
|
36
36
|
export interface AgentToolSettlementHookV1 { settle(event: AgentToolSettlementEventV1, context: AgentToolHookContextV1): AgentToolSettlementResultV1 | Promise<AgentToolSettlementResultV1>; }
|
|
37
37
|
export interface AgentToolObserverEventV1 { readonly type: "agent-tool.observer/v1"; readonly settlement: AgentToolSettlementEventV1; }
|
|
38
38
|
export interface AgentToolObserverHookV1 { observe(event: AgentToolObserverEventV1, context: AgentToolHookContextV1): void | Promise<void>; }
|
|
39
|
-
export interface AgentToolAuditHookProjectionV1 extends AgentToolHookRefV1 { readonly tier: "platform" | "application"; readonly order: number; readonly outcome: "allowed" | "denied" | "recorded" | "failed" | "timed-out" | "cancelled" | "invalid-result"; readonly reasonCode?: string; readonly evidence?: JsonObject; }
|
|
39
|
+
export interface AgentToolAuditHookProjectionV1 extends AgentToolHookRefV1 { readonly tier: "platform" | "application"; readonly order: number; readonly outcome: "allowed" | "denied" | "recorded" | "failed" | "timed-out" | "cancelled" | "invalid-result"; readonly reasonCode?: string; readonly message?: string; readonly evidence?: JsonObject; }
|
package/core/agent/README.md
CHANGED
|
@@ -100,6 +100,21 @@ Tool input and output are validated by the model validator
|
|
|
100
100
|
otherwise. Audit entries redact `input`, `result` and execution `metadata`
|
|
101
101
|
by default using `bazis/library/redaction`.
|
|
102
102
|
|
|
103
|
+
A tool reports a failure to the model with `throw new ToolError("Product
|
|
104
|
+
kiwi is no longer sold")`: the message reaches the model as written. Any other
|
|
105
|
+
exception from `execute` reaches the model as `Tool execution failed.` — internal
|
|
106
|
+
details stay out of the LLM — and goes to the application log with its stack
|
|
107
|
+
(`Agent tool failed`). Failures of the model itself are logged as `Agent model
|
|
108
|
+
call failed`. The LLM connectors pass the application logger; a runtime created
|
|
109
|
+
by hand takes `options.logger`.
|
|
110
|
+
|
|
111
|
+
An enforcement hook that denies a call explains why with `message` — it reaches
|
|
112
|
+
the model as written, with `reasonCode` in `error.details`, and both go to the
|
|
113
|
+
audit. A hook that throws, times out or returns an invalid answer is logged as
|
|
114
|
+
`Agent tool hook failed` with the error. `llmConnect` and `llmRouter` build
|
|
115
|
+
`AgentRuntime` at start, so wrong runtime options or enforcement hooks without
|
|
116
|
+
an audit sink stop the application with a configuration error.
|
|
117
|
+
|
|
103
118
|
Retries are opt-in. `write` and `external` tools need an idempotency key before
|
|
104
119
|
the executor repeats them. Runtime-generated keys include both the invocation id
|
|
105
120
|
and the unique model call id; `toolExecution.idempotencyKeyForCall` gives a
|
package/core/agent/errors.ts
CHANGED
|
@@ -22,12 +22,25 @@ export class AgentToolExecutionError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* A failure a tool reports to the model on purpose: the message reaches the
|
|
27
|
+
* model as written, so the model can explain it or try another way. Any other
|
|
28
|
+
* exception from `execute` reaches the model as "Tool execution failed." and
|
|
29
|
+
* goes to the application log with its stack.
|
|
30
|
+
*/
|
|
31
|
+
export class ToolError extends Error {
|
|
32
|
+
constructor(message: string) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "ToolError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
/**
|
|
26
39
|
* Explicit assertion by a tool that execution failed before any side effect
|
|
27
40
|
* could commit. Only this typed error keeps write/external failures eligible
|
|
28
|
-
* for an idempotency-gated retry.
|
|
41
|
+
* for an idempotency-gated retry. Its message reaches the model like a ToolError.
|
|
29
42
|
*/
|
|
30
|
-
export class AgentToolPreCommitError extends
|
|
43
|
+
export class AgentToolPreCommitError extends ToolError {
|
|
31
44
|
constructor(message: string) {
|
|
32
45
|
super(message);
|
|
33
46
|
this.name = "AgentToolPreCommitError";
|
package/core/agent/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { describeTool } from "../Tool.contract";
|
|
|
3
3
|
import type { ServiceProvider } from "../../di";
|
|
4
4
|
import { getGeneratedOpenApiMetadata } from "../../http/OpenApi/generatedOpenApiRegistry";
|
|
5
5
|
import { redactSensitive, redactSensitiveText } from "../../../library/redaction";
|
|
6
|
+
import type { Logger } from "../../kernel/logging/Logger";
|
|
6
7
|
import { AgentRuntimeError } from "../errors";
|
|
7
8
|
import { validateAgentJsonSchema } from "./AgentJsonSchema";
|
|
8
9
|
import type { AgentDefinition, AgentRegistry } from "../AgentRegistry";
|
|
@@ -80,6 +81,11 @@ export interface AgentRuntimeOptions {
|
|
|
80
81
|
readonly contextBuilder?: AgentContextBuilder;
|
|
81
82
|
readonly contextLimits?: AgentContextLimits;
|
|
82
83
|
readonly toolExecutorOptions?: AgentToolExecutorOptions;
|
|
84
|
+
/**
|
|
85
|
+
* Where failures of the model and unexpected tool failures go, with their
|
|
86
|
+
* stack. The LLM connectors pass the application logger.
|
|
87
|
+
*/
|
|
88
|
+
readonly logger?: Logger;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
export interface AgentRuntimeInvokeOptions {
|
|
@@ -503,6 +509,7 @@ export class AgentExecutionDriver {
|
|
|
503
509
|
readonly defaultTimeoutMs?: number;
|
|
504
510
|
private readonly defaultContextLimits?: AgentContextLimits;
|
|
505
511
|
private readonly contextBuilder: AgentContextBuilder;
|
|
512
|
+
private readonly logger?: Logger;
|
|
506
513
|
|
|
507
514
|
constructor(
|
|
508
515
|
services: ServiceProvider,
|
|
@@ -521,7 +528,8 @@ export class AgentExecutionDriver {
|
|
|
521
528
|
: options.timeoutMs ?? DEFAULT_MODEL_TIMEOUT_MS;
|
|
522
529
|
this.defaultContextLimits = normalizeAgentContextLimits(options.contextLimits, "AgentRuntime.contextLimits");
|
|
523
530
|
this.contextBuilder = options.contextBuilder ?? new DefaultAgentContextBuilder();
|
|
524
|
-
this.
|
|
531
|
+
this.logger = options.logger;
|
|
532
|
+
this.toolExecutor = new AgentToolExecutor(services, registry, options.toolExecutorOptions, options.logger);
|
|
525
533
|
}
|
|
526
534
|
|
|
527
535
|
async invoke(agentName: string, options: AgentRuntimeInvokeOptions = {}): Promise<AgentRuntimeResult> {
|
|
@@ -1122,6 +1130,7 @@ export class AgentExecutionDriver {
|
|
|
1122
1130
|
try {
|
|
1123
1131
|
return { ok: true, value: normalizeProviderResponse(rawResponse) };
|
|
1124
1132
|
} catch (error) {
|
|
1133
|
+
this.logger?.error("Agent model call failed", { agent: agentName, invocationId: request.invocationId, code: "AGENT_PROVIDER_RESPONSE_INVALID", error: redactSensitive(error) });
|
|
1125
1134
|
return {
|
|
1126
1135
|
ok: false,
|
|
1127
1136
|
error: runtimeError("AGENT_PROVIDER_RESPONSE_INVALID", `Model provider returned an invalid response: ${errorMessageOf(error)}`),
|
|
@@ -1133,6 +1142,9 @@ export class AgentExecutionDriver {
|
|
|
1133
1142
|
: error instanceof AgentRuntimeAbortError
|
|
1134
1143
|
? "AGENT_ABORTED"
|
|
1135
1144
|
: "AGENT_PROVIDER_FAILED";
|
|
1145
|
+
if (code !== "AGENT_ABORTED") {
|
|
1146
|
+
this.logger?.error("Agent model call failed", { agent: agentName, invocationId: request.invocationId, code, error: redactSensitive(error) });
|
|
1147
|
+
}
|
|
1136
1148
|
return { ok: false, error: runtimeError(code, errorMessageOf(error)) };
|
|
1137
1149
|
} finally {
|
|
1138
1150
|
acceptingText = false;
|
|
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
|
|
|
232
232
|
if (method === "account/updated") this.modelCache = undefined;
|
|
233
233
|
}, () => { this.clearLogin(); this.modelCache = undefined; });
|
|
234
234
|
const initialized = await client.request("initialize", {
|
|
235
|
-
clientInfo: { name: "bazis", title: "Bazis", version: "0.99.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.99.3" }, capabilities: { experimentalApi: true },
|
|
236
236
|
}, 15_000, signal);
|
|
237
237
|
if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
|
|
238
238
|
client.notify("initialized");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
|
|
2
2
|
import { AGENT_MODEL_PROVIDER, AGENT_RUNTIME_OPTIONS, AgentRuntime, type AgentModelProvider } from "../../agent";
|
|
3
|
-
import { DI, SERVICE_PROVIDER, type ProviderDefinition } from "../../di";
|
|
3
|
+
import { DI, DiError, HOSTED_SERVICE, SERVICE_PROVIDER, type ProviderDefinition } from "../../di";
|
|
4
|
+
import { LOGGER } from "../../kernel/logging/Logger";
|
|
4
5
|
import { reader, requireValue } from "../connectorConfig";
|
|
5
6
|
import { InfraError, type InfraConnector } from "../InfraConnector";
|
|
6
7
|
import { identifyConnector } from "../connectorIdentity";
|
|
@@ -62,8 +63,22 @@ export function resolveLlmConnectionOptions<T extends LlmConfigShape>(
|
|
|
62
63
|
export const agentRuntimeProviders: readonly ProviderDefinition[] = Object.freeze([
|
|
63
64
|
DI.singleton(DI.factoryProviderWithResolver(AgentRuntime, [AGENT_MODEL_PROVIDER], (resolver, model: AgentModelProvider) => {
|
|
64
65
|
const services = resolver.resolve(SERVICE_PROVIDER);
|
|
65
|
-
|
|
66
|
+
const options = services.has(AGENT_RUNTIME_OPTIONS) ? services.resolve(AGENT_RUNTIME_OPTIONS) : {};
|
|
67
|
+
return new AgentRuntime(services, model, { ...options, logger: options.logger ?? services.tryResolve(LOGGER) });
|
|
66
68
|
})),
|
|
69
|
+
// Builds the runtime at start: wrong options or hooks without an audit sink
|
|
70
|
+
// stop the application instead of failing every agent request.
|
|
71
|
+
DI.singleton(DI.factoryProviderWithResolver(HOSTED_SERVICE, [], (resolver) => ({
|
|
72
|
+
start() {
|
|
73
|
+
try {
|
|
74
|
+
resolver.resolve(AgentRuntime);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof DiError) throw error;
|
|
77
|
+
throw new DiError(`AgentRuntime: ${error instanceof Error ? error.message : String(error)}`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
stop() {},
|
|
81
|
+
}))),
|
|
67
82
|
]);
|
|
68
83
|
|
|
69
84
|
export function llmConnect<T extends LlmConfigShape>(
|
package/index.ts
CHANGED
package/package.json
CHANGED