bazis 0.99.2 → 0.99.4
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.
|
@@ -327,14 +327,14 @@ function isSettlementResult(value: unknown): value is { readonly status: "record
|
|
|
327
327
|
} catch { return false; }
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
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 };
|
|
331
331
|
|
|
332
332
|
async function awaitHook<T>(operation: Promise<T>, signal: AbortSignal | undefined, timeoutMs: number | undefined): Promise<HookOutcome<T>> {
|
|
333
333
|
// Observe both branches even after the caller's deadline wins: a late hook
|
|
334
334
|
// rejection must never become an unhandled process-level rejection.
|
|
335
335
|
const observed: Promise<HookOutcome<T>> = operation
|
|
336
336
|
.then((value): HookOutcome<T> => ({ kind: "value", value }))
|
|
337
|
-
.catch((): HookOutcome<T> => ({ kind: "failed" }));
|
|
337
|
+
.catch((error: unknown): HookOutcome<T> => ({ kind: "failed", error }));
|
|
338
338
|
if (signal?.aborted) return { kind: "cancelled" };
|
|
339
339
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
340
340
|
let detach: (() => void) | undefined;
|
|
@@ -646,6 +646,7 @@ export class AgentToolExecutor {
|
|
|
646
646
|
tier: this.isPlatformHook(record) ? "platform" : "application", order: registration.order ?? 0,
|
|
647
647
|
outcome: "failed",
|
|
648
648
|
};
|
|
649
|
+
let hookError: unknown;
|
|
649
650
|
const postEffect = state.executeStarted;
|
|
650
651
|
const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
|
|
651
652
|
const remainingMs = !postEffect && state.deadlineUnixMs !== undefined
|
|
@@ -683,6 +684,7 @@ export class AgentToolExecutor {
|
|
|
683
684
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, boundedTimeoutMs);
|
|
684
685
|
if (activated.kind !== "value") {
|
|
685
686
|
if (activated.kind === "timeout") controller.abort();
|
|
687
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
686
688
|
projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? (state.callerSignal?.aborted ? "cancelled" : "timed-out") : "failed" };
|
|
687
689
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement handler was unavailable.", metadata, startedAt);
|
|
688
690
|
} else {
|
|
@@ -695,6 +697,7 @@ export class AgentToolExecutor {
|
|
|
695
697
|
const outcome = await awaitHook(Promise.resolve((handler.settle as (value: AgentToolSettlementEventV1, context: AgentToolHookContextV1) => unknown)(event, context)), controller.signal, invocationTimeoutMs);
|
|
696
698
|
if (outcome.kind === "timeout") controller.abort();
|
|
697
699
|
if (outcome.kind !== "value") {
|
|
700
|
+
if (outcome.kind === "failed") hookError = outcome.error;
|
|
698
701
|
projection = { ...projection, outcome: outcome.kind === "timeout" ? "timed-out" : outcome.kind === "cancelled" ? (state.callerSignal?.aborted ? "cancelled" : "timed-out") : "failed" };
|
|
699
702
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement failed.", metadata, startedAt);
|
|
700
703
|
} else if (!isSettlementResult(outcome.value)) {
|
|
@@ -707,17 +710,20 @@ export class AgentToolExecutor {
|
|
|
707
710
|
}
|
|
708
711
|
}
|
|
709
712
|
}
|
|
710
|
-
} catch {
|
|
713
|
+
} catch (error) {
|
|
714
|
+
hookError = error;
|
|
711
715
|
projection = { ...projection, outcome: "failed" };
|
|
712
716
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement failed.", metadata, startedAt);
|
|
713
717
|
} finally {
|
|
714
718
|
detach?.();
|
|
715
719
|
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
716
720
|
if (disposeError !== undefined) {
|
|
721
|
+
hookError ??= disposeError;
|
|
717
722
|
projection = { ...projection, outcome: "failed" };
|
|
718
723
|
terminal = this.errorResult(call, "TOOL_HOOK_SETTLEMENT_FAILED", "Agent Tool settlement disposal failed.", metadata, startedAt);
|
|
719
724
|
}
|
|
720
725
|
}
|
|
726
|
+
this.logHookFailure(projection, call, event.agentName, hookError);
|
|
721
727
|
const evidenceRemainingMs = postEffect || state.deadlineUnixMs === undefined
|
|
722
728
|
? registration.timeoutMs ?? 5_000
|
|
723
729
|
: Math.max(0, Math.min(registration.timeoutMs ?? 5_000, state.deadlineUnixMs - Date.now()));
|
|
@@ -744,6 +750,7 @@ export class AgentToolExecutor {
|
|
|
744
750
|
if (registration.kind !== "observer") continue;
|
|
745
751
|
const scope = this.services.createScope();
|
|
746
752
|
let outcome: AgentToolAuditHookProjectionV1["outcome"] = "failed";
|
|
753
|
+
let hookError: unknown;
|
|
747
754
|
const timeoutMs = registration.timeoutMs ?? 5_000;
|
|
748
755
|
const controller = new AbortController();
|
|
749
756
|
const detach = attachAbortForwarding(options.signal, controller);
|
|
@@ -752,6 +759,7 @@ export class AgentToolExecutor {
|
|
|
752
759
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
|
|
753
760
|
if (activated.kind !== "value") {
|
|
754
761
|
if (activated.kind === "timeout") controller.abort();
|
|
762
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
755
763
|
outcome = activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed";
|
|
756
764
|
} else {
|
|
757
765
|
const handler = activated.value as { observe?: unknown };
|
|
@@ -764,15 +772,22 @@ export class AgentToolExecutor {
|
|
|
764
772
|
Math.max(1, context.deadlineUnixMs - Date.now()),
|
|
765
773
|
);
|
|
766
774
|
if (observed.kind === "timeout" || observed.kind === "value") controller.abort();
|
|
775
|
+
if (observed.kind === "failed") hookError = observed.error;
|
|
767
776
|
outcome = observed.kind === "value" ? "recorded" : observed.kind === "timeout" ? "timed-out" : observed.kind === "cancelled" ? "cancelled" : "failed";
|
|
768
777
|
}
|
|
769
778
|
}
|
|
770
|
-
} catch {
|
|
779
|
+
} catch (error) {
|
|
780
|
+
hookError = error;
|
|
771
781
|
outcome = controller.signal.aborted && options.signal?.aborted ? "cancelled" : "failed";
|
|
772
782
|
} finally {
|
|
773
783
|
detach?.();
|
|
774
|
-
|
|
784
|
+
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
785
|
+
if (disposeError !== undefined) {
|
|
786
|
+
hookError ??= disposeError;
|
|
787
|
+
outcome = "failed";
|
|
788
|
+
}
|
|
775
789
|
}
|
|
790
|
+
this.logHookFailure({ kind: registration.kind, id: registration.id, version: registration.version, outcome }, { id: settlement.callId, name: settlement.toolName }, settlement.agentName, hookError);
|
|
776
791
|
await this.recordObserverHook(settlement, registration, record, outcome, timeoutMs);
|
|
777
792
|
}
|
|
778
793
|
}
|
|
@@ -796,7 +811,8 @@ export class AgentToolExecutor {
|
|
|
796
811
|
});
|
|
797
812
|
// Observer evidence is deliberately best effort and uses an independent
|
|
798
813
|
// bounded write: it cannot alter the already-terminal Tool result.
|
|
799
|
-
await awaitHook(Promise.resolve().then(() => auditSinkFunction(this.auditSink!)(entry)), undefined, timeoutMs);
|
|
814
|
+
const recorded = await awaitHook(Promise.resolve().then(() => auditSinkFunction(this.auditSink!)(entry)), undefined, timeoutMs);
|
|
815
|
+
if (recorded.kind !== "value") this.logAuditFailure("observer", settlement.toolName, settlement.callId, settlement.agentName, "best-effort", recorded);
|
|
800
816
|
}
|
|
801
817
|
|
|
802
818
|
private async executePipeline(
|
|
@@ -1086,6 +1102,7 @@ export class AgentToolExecutor {
|
|
|
1086
1102
|
tier: isPlatform(record) ? "platform" : "application", order: registration.order ?? 0,
|
|
1087
1103
|
outcome: "failed",
|
|
1088
1104
|
};
|
|
1105
|
+
let hookError: unknown;
|
|
1089
1106
|
const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
|
|
1090
1107
|
const remainingMs = executionState.deadlineUnixMs === undefined
|
|
1091
1108
|
? undefined : Math.max(0, executionState.deadlineUnixMs - Date.now());
|
|
@@ -1102,6 +1119,7 @@ export class AgentToolExecutor {
|
|
|
1102
1119
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
|
|
1103
1120
|
if (activated.kind !== "value") {
|
|
1104
1121
|
if (activated.kind === "timeout") controller.abort();
|
|
1122
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
1105
1123
|
projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed" };
|
|
1106
1124
|
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);
|
|
1107
1125
|
} else {
|
|
@@ -1113,27 +1131,31 @@ export class AgentToolExecutor {
|
|
|
1113
1131
|
if (outcome.kind === "timeout") controller.abort();
|
|
1114
1132
|
if (outcome.kind === "timeout") { projection = { ...projection, outcome: "timed-out" }; terminal = this.errorResult(call, "TOOL_HOOK_TIMEOUT", "Agent Tool enforcement hook timed out.", metadata, startedAt); }
|
|
1115
1133
|
else if (outcome.kind === "cancelled") { projection = { ...projection, outcome: "cancelled" }; terminal = this.errorResult(call, "TOOL_HOOK_CANCELLED", "Agent Tool enforcement hook was cancelled.", metadata, startedAt); }
|
|
1116
|
-
else if (outcome.kind === "failed") { terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt); }
|
|
1134
|
+
else if (outcome.kind === "failed") { hookError = outcome.error; terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt); }
|
|
1117
1135
|
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); }
|
|
1118
1136
|
else {
|
|
1119
1137
|
const decision = outcome.value;
|
|
1120
|
-
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 } : {}) };
|
|
1121
|
-
|
|
1138
|
+
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 } : {}) };
|
|
1139
|
+
// The hook's message is meant for the model, like a ToolError; reasonCode helps it branch.
|
|
1140
|
+
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);
|
|
1122
1141
|
}
|
|
1123
1142
|
}
|
|
1124
1143
|
}
|
|
1125
1144
|
}
|
|
1126
|
-
} catch {
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
hookError = error;
|
|
1127
1147
|
projection = { ...projection, outcome: "failed" };
|
|
1128
1148
|
terminal = this.errorResult(call, options.signal?.aborted ? "TOOL_HOOK_CANCELLED" : "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt);
|
|
1129
1149
|
} finally {
|
|
1130
1150
|
detach?.();
|
|
1131
1151
|
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
1132
1152
|
if (disposeError !== undefined) {
|
|
1153
|
+
hookError ??= disposeError;
|
|
1133
1154
|
projection = { ...projection, outcome: "failed" };
|
|
1134
1155
|
terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook disposal failed.", metadata, startedAt);
|
|
1135
1156
|
}
|
|
1136
1157
|
}
|
|
1158
|
+
this.logHookFailure(projection, call, agentName, hookError);
|
|
1137
1159
|
const evidenceRemainingMs = executionState.deadlineUnixMs === undefined
|
|
1138
1160
|
? registration.timeoutMs ?? 5_000
|
|
1139
1161
|
: Math.max(0, Math.min(registration.timeoutMs ?? 5_000, executionState.deadlineUnixMs - Date.now()));
|
|
@@ -1174,8 +1196,12 @@ export class AgentToolExecutor {
|
|
|
1174
1196
|
if (!this.auditSink) return true;
|
|
1175
1197
|
try {
|
|
1176
1198
|
const outcome = await awaitHook(Promise.resolve().then(() => auditSinkFunction(this.auditSink!)({ kind: "agent-tool", phase, callId: call.id, toolName: tool.metadata.name, input: redactSensitive(normalizeJsonValue(call.input, "hook.audit.input"), this.hookRedaction), tool: { name: tool.metadata.name, sideEffect: tool.metadata.sideEffect, approval: tool.metadata.approval, tags: tool.metadata.tags ?? [] }, startedAtUnixMs: startedAt, finishedAtUnixMs: Date.now(), durationMs: Date.now() - startedAt, metadata: redactSensitive(metadata, this.hookRedaction) as JsonObject, hook })), signal, timeoutMs);
|
|
1199
|
+
if (outcome.kind !== "value") this.logAuditFailure(phase, call.name, call.id, options.agentName, "required", outcome);
|
|
1177
1200
|
return outcome.kind !== "value";
|
|
1178
|
-
} catch {
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
this.logAuditFailure(phase, call.name, call.id, options.agentName, "required", { kind: "failed", error });
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1179
1205
|
}
|
|
1180
1206
|
|
|
1181
1207
|
private frameworkTerminalAuditTimeout(remainingMs: number | undefined): number {
|
|
@@ -1195,8 +1221,12 @@ export class AgentToolExecutor {
|
|
|
1195
1221
|
result: redactSensitive(result, this.hookRedaction) as AgentToolResult,
|
|
1196
1222
|
startedAtUnixMs: startedAt, finishedAtUnixMs: Date.now(), durationMs: Date.now() - startedAt,
|
|
1197
1223
|
})), signal, timeoutMs);
|
|
1224
|
+
if (outcome.kind !== "value") this.logAuditFailure("result", result.name, result.callId, undefined, "required", outcome);
|
|
1198
1225
|
return outcome.kind !== "value";
|
|
1199
|
-
} catch {
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
this.logAuditFailure("result", result.name, result.callId, undefined, "required", { kind: "failed", error });
|
|
1228
|
+
return true;
|
|
1229
|
+
}
|
|
1200
1230
|
}
|
|
1201
1231
|
|
|
1202
1232
|
private hasMandatoryHooks(): boolean {
|
|
@@ -1944,6 +1974,7 @@ export class AgentToolExecutor {
|
|
|
1944
1974
|
(error: unknown) => ({ ok: false, error } as const),
|
|
1945
1975
|
);
|
|
1946
1976
|
const outcome = await awaitHook(settled, signal, timeoutMs);
|
|
1977
|
+
if (outcome.kind === "timeout") this.logAuditFailure(entry.phase, entry.toolName, entry.callId, entry.agentName, audit.failureMode, outcome);
|
|
1947
1978
|
if (outcome.kind !== "value") return outcome;
|
|
1948
1979
|
return outcome.value.ok
|
|
1949
1980
|
? { kind: "value", value: undefined }
|
|
@@ -1959,6 +1990,8 @@ export class AgentToolExecutor {
|
|
|
1959
1990
|
originalResult: AgentToolResult | undefined,
|
|
1960
1991
|
error: unknown,
|
|
1961
1992
|
): AgentToolResult | undefined {
|
|
1993
|
+
// Logged in both modes: in best-effort the call goes on, and the lost entry is visible only here.
|
|
1994
|
+
this.logAuditFailure(entry.phase, entry.toolName, entry.callId, entry.agentName, audit.failureMode, { kind: "failed", error });
|
|
1962
1995
|
if (audit.failureMode === "best-effort") return undefined;
|
|
1963
1996
|
const sideEffectingSuccess = entry.phase === "result"
|
|
1964
1997
|
&& originalResult?.status === "success"
|
|
@@ -1970,8 +2003,8 @@ export class AgentToolExecutor {
|
|
|
1970
2003
|
call,
|
|
1971
2004
|
outcomeUnknown ? "TOOL_AUDIT_FAILED_OUTCOME_UNKNOWN" : "TOOL_AUDIT_FAILED",
|
|
1972
2005
|
outcomeUnknown
|
|
1973
|
-
?
|
|
1974
|
-
:
|
|
2006
|
+
? "Tool result audit failed after an outcome-unknown side-effecting operation; retry safety is unknown."
|
|
2007
|
+
: "Tool audit failed.",
|
|
1975
2008
|
metadata,
|
|
1976
2009
|
startedAt,
|
|
1977
2010
|
{
|
|
@@ -2021,12 +2054,12 @@ export class AgentToolExecutor {
|
|
|
2021
2054
|
.then(async (result) => {
|
|
2022
2055
|
const error = await this.disposeScope(scope, scopeDisposeTimeoutMs);
|
|
2023
2056
|
if (error !== undefined) {
|
|
2024
|
-
|
|
2057
|
+
this.reportDeferred("Agent tool scope disposal failed", { tool: call.name, callId: call.id, error: redactSensitive(error) });
|
|
2025
2058
|
}
|
|
2026
2059
|
await onSettled(result);
|
|
2027
2060
|
})
|
|
2028
2061
|
.catch((error: unknown) => {
|
|
2029
|
-
|
|
2062
|
+
this.reportDeferred("Agent tool settlement handling failed", { tool: call.name, callId: call.id, error: redactSensitive(error) });
|
|
2030
2063
|
});
|
|
2031
2064
|
}
|
|
2032
2065
|
|
|
@@ -2035,11 +2068,50 @@ export class AgentToolExecutor {
|
|
|
2035
2068
|
try {
|
|
2036
2069
|
await auditSinkFunction(audit.sink)(entry);
|
|
2037
2070
|
} catch (error) {
|
|
2038
|
-
|
|
2071
|
+
this.logAuditFailure(entry.phase, entry.toolName, entry.callId, entry.agentName, audit.failureMode, { kind: "failed", error });
|
|
2039
2072
|
}
|
|
2040
2073
|
}
|
|
2041
2074
|
|
|
2042
2075
|
|
|
2076
|
+
/**
|
|
2077
|
+
* The audit sink did not record an entry. `mode` is how the call treated it:
|
|
2078
|
+
* `fail-closed` stopped it, `best-effort` let it go on, `required` is hook evidence.
|
|
2079
|
+
*/
|
|
2080
|
+
private logAuditFailure(
|
|
2081
|
+
phase: string,
|
|
2082
|
+
toolName: string,
|
|
2083
|
+
callId: string,
|
|
2084
|
+
agentName: string | undefined,
|
|
2085
|
+
mode: AgentToolAuditFailureMode | "required",
|
|
2086
|
+
outcome: HookOutcome<unknown>,
|
|
2087
|
+
): void {
|
|
2088
|
+
if (outcome.kind === "value" || outcome.kind === "cancelled") return;
|
|
2089
|
+
this.logger?.error("Agent tool audit failed", {
|
|
2090
|
+
phase, mode, agent: agentName ?? null, tool: toolName, callId,
|
|
2091
|
+
...(outcome.kind === "timeout" ? { reason: "timed out" } : outcome.error === undefined ? {} : { error: redactSensitive(outcome.error) }),
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/** Late failures after the call returned; without an application logger they still reach stderr. */
|
|
2096
|
+
private reportDeferred(message: string, fields: Record<string, unknown>): void {
|
|
2097
|
+
if (this.logger) this.logger.error(message, fields);
|
|
2098
|
+
else console.error(`[agent-tool] ${message}`, fields);
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
/** A hook that failed, timed out or answered nonsense; a cancelled run is not a failure. */
|
|
2102
|
+
private logHookFailure(
|
|
2103
|
+
hook: Pick<AgentToolAuditHookProjectionV1, "kind" | "id" | "version" | "outcome">,
|
|
2104
|
+
call: Pick<AgentToolCall, "id" | "name">,
|
|
2105
|
+
agentName: string | undefined,
|
|
2106
|
+
error: unknown,
|
|
2107
|
+
): void {
|
|
2108
|
+
if (hook.outcome !== "failed" && hook.outcome !== "timed-out" && hook.outcome !== "invalid-result") return;
|
|
2109
|
+
this.logger?.error("Agent tool hook failed", {
|
|
2110
|
+
hook: `${hook.kind}:${hook.id}@${hook.version}`, outcome: hook.outcome, agent: agentName ?? null, tool: call.name, callId: call.id,
|
|
2111
|
+
...(error === undefined ? {} : { error: redactSensitive(error) }),
|
|
2112
|
+
});
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2043
2115
|
/**
|
|
2044
2116
|
* A tool's own failure: a ToolError message reaches the model as written;
|
|
2045
2117
|
* anything else is replaced by `fallback` and logged with its stack.
|
|
@@ -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
|
@@ -108,6 +108,17 @@ details stay out of the LLM — and goes to the application log with its stack
|
|
|
108
108
|
call failed`. The LLM connectors pass the application logger; a runtime created
|
|
109
109
|
by hand takes `options.logger`.
|
|
110
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
|
+
|
|
118
|
+
An audit sink that fails is logged as `Agent tool audit failed` with the
|
|
119
|
+
phase and mode, in `best-effort` too, where the call goes on; the model sees
|
|
120
|
+
only `Tool audit failed.`.
|
|
121
|
+
|
|
111
122
|
Retries are opt-in. `write` and `external` tools need an idempotency key before
|
|
112
123
|
the executor repeats them. Runtime-generated keys include both the invocation id
|
|
113
124
|
and the unique model call id; `toolExecution.idempotencyKeyForCall` gives a
|
|
@@ -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.4" }, 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,6 @@
|
|
|
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
4
|
import { LOGGER } from "../../kernel/logging/Logger";
|
|
5
5
|
import { reader, requireValue } from "../connectorConfig";
|
|
6
6
|
import { InfraError, type InfraConnector } from "../InfraConnector";
|
|
@@ -66,6 +66,19 @@ export const agentRuntimeProviders: readonly ProviderDefinition[] = Object.freez
|
|
|
66
66
|
const options = services.has(AGENT_RUNTIME_OPTIONS) ? services.resolve(AGENT_RUNTIME_OPTIONS) : {};
|
|
67
67
|
return new AgentRuntime(services, model, { ...options, logger: options.logger ?? services.tryResolve(LOGGER) });
|
|
68
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
|
+
}))),
|
|
69
82
|
]);
|
|
70
83
|
|
|
71
84
|
export function llmConnect<T extends LlmConfigShape>(
|
package/package.json
CHANGED