bazis 0.99.2 → 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.
|
@@ -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
|
}
|
|
@@ -1086,6 +1101,7 @@ export class AgentToolExecutor {
|
|
|
1086
1101
|
tier: isPlatform(record) ? "platform" : "application", order: registration.order ?? 0,
|
|
1087
1102
|
outcome: "failed",
|
|
1088
1103
|
};
|
|
1104
|
+
let hookError: unknown;
|
|
1089
1105
|
const configuredTimeoutMs = registration.timeoutMs ?? 5_000;
|
|
1090
1106
|
const remainingMs = executionState.deadlineUnixMs === undefined
|
|
1091
1107
|
? undefined : Math.max(0, executionState.deadlineUnixMs - Date.now());
|
|
@@ -1102,6 +1118,7 @@ export class AgentToolExecutor {
|
|
|
1102
1118
|
const activated = await awaitHook(record.activation.activateAsync(scope), controller.signal, timeoutMs);
|
|
1103
1119
|
if (activated.kind !== "value") {
|
|
1104
1120
|
if (activated.kind === "timeout") controller.abort();
|
|
1121
|
+
if (activated.kind === "failed") hookError = activated.error;
|
|
1105
1122
|
projection = { ...projection, outcome: activated.kind === "timeout" ? "timed-out" : activated.kind === "cancelled" ? "cancelled" : "failed" };
|
|
1106
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);
|
|
1107
1124
|
} else {
|
|
@@ -1113,27 +1130,31 @@ export class AgentToolExecutor {
|
|
|
1113
1130
|
if (outcome.kind === "timeout") controller.abort();
|
|
1114
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); }
|
|
1115
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); }
|
|
1116
|
-
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); }
|
|
1117
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); }
|
|
1118
1135
|
else {
|
|
1119
1136
|
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
|
-
|
|
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);
|
|
1122
1140
|
}
|
|
1123
1141
|
}
|
|
1124
1142
|
}
|
|
1125
1143
|
}
|
|
1126
|
-
} catch {
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
hookError = error;
|
|
1127
1146
|
projection = { ...projection, outcome: "failed" };
|
|
1128
1147
|
terminal = this.errorResult(call, options.signal?.aborted ? "TOOL_HOOK_CANCELLED" : "TOOL_HOOK_FAILED", "Agent Tool enforcement hook failed.", metadata, startedAt);
|
|
1129
1148
|
} finally {
|
|
1130
1149
|
detach?.();
|
|
1131
1150
|
const disposeError = await this.disposeScope(scope, this.scopeDisposeTimeoutMs);
|
|
1132
1151
|
if (disposeError !== undefined) {
|
|
1152
|
+
hookError ??= disposeError;
|
|
1133
1153
|
projection = { ...projection, outcome: "failed" };
|
|
1134
1154
|
terminal = this.errorResult(call, "TOOL_HOOK_FAILED", "Agent Tool enforcement hook disposal failed.", metadata, startedAt);
|
|
1135
1155
|
}
|
|
1136
1156
|
}
|
|
1157
|
+
this.logHookFailure(projection, call, agentName, hookError);
|
|
1137
1158
|
const evidenceRemainingMs = executionState.deadlineUnixMs === undefined
|
|
1138
1159
|
? registration.timeoutMs ?? 5_000
|
|
1139
1160
|
: Math.max(0, Math.min(registration.timeoutMs ?? 5_000, executionState.deadlineUnixMs - Date.now()));
|
|
@@ -2040,6 +2061,20 @@ export class AgentToolExecutor {
|
|
|
2040
2061
|
}
|
|
2041
2062
|
|
|
2042
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
|
+
|
|
2043
2078
|
/**
|
|
2044
2079
|
* A tool's own failure: a ToolError message reaches the model as written;
|
|
2045
2080
|
* 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,13 @@ 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
|
+
|
|
111
118
|
Retries are opt-in. `write` and `external` tools need an idempotency key before
|
|
112
119
|
the executor repeats them. Runtime-generated keys include both the invocation id
|
|
113
120
|
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.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,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