@theoremjs/agents 0.3.4-next.524.g5000023 → 0.3.4-next.528.g44c76f9
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/esm/src/guardrails/error.d.ts +5 -1
- package/esm/src/guardrails/error.js +44 -1
- package/esm/src/guardrails/network.js +3 -3
- package/esm/src/kernel/engine/decision.d.ts +10 -0
- package/esm/src/kernel/engine/decision.js +34 -18
- package/esm/src/kernel/tools/coordinate.js +6 -6
- package/esm/src/kernel/tools/execute.js +5 -4
- package/esm/src/kernel/tools/memory-execution.js +11 -7
- package/esm/src/kernel/tools/operation.d.ts +2 -1
- package/esm/src/kernel/tools/operation.js +13 -11
- package/esm/src/kernel/tools/remote.js +8 -1
- package/esm/src/providers/shared/retry.js +1 -2
- package/esm/src/providers/shared/upstream-tap.d.ts +0 -2
- package/esm/src/providers/shared/upstream-tap.js +2 -16
- package/package.json +1 -1
|
@@ -18,6 +18,10 @@ declare function errorCopyText(copy: ErrorCopies, labels?: LabelOverrides): stri
|
|
|
18
18
|
declare function publicError(err: unknown, labels?: LabelOverrides): string;
|
|
19
19
|
/** Raw diagnostic for hosts, traces, and logs; never shown to end users. Each cause that says something new follows on its own line. */
|
|
20
20
|
declare function describeError(err: unknown): string;
|
|
21
|
+
/** fetch rejects only when no response came back, so any non-abort, non-timeout rejection is `network`. */
|
|
22
|
+
declare function networkError(err: unknown): unknown;
|
|
23
|
+
/** `send`, with a failed send or body read named as `networkError` names it. */
|
|
24
|
+
declare function sendOrNetworkError(send: typeof fetch, ...args: Parameters<typeof fetch>): Promise<Response>;
|
|
21
25
|
type ProducedError = TurnEventOf<'error'> & {
|
|
22
26
|
errorInternal: string;
|
|
23
27
|
};
|
|
@@ -28,4 +32,4 @@ declare function withPublicLabels(event: TurnEvent, labels?: LabelOverrides): Tu
|
|
|
28
32
|
export type { ErrorCopies, ErrorCopy, ErrorKind, TheoremErrorOptions } from './theorem-error.js';
|
|
29
33
|
export { ERROR_KINDS } from './theorem-error.js';
|
|
30
34
|
export type { ProducedError };
|
|
31
|
-
export { describeError, errorCopyText, errorKind, isAbortError, isTimeoutError, kindOfHttpStatus, publicError, TheoremError, throwIfAborted, toErrorEvent, withPublicLabels, };
|
|
35
|
+
export { describeError, errorCopyText, errorKind, isAbortError, isTimeoutError, kindOfHttpStatus, networkError, publicError, sendOrNetworkError, TheoremError, throwIfAborted, toErrorEvent, withPublicLabels, };
|
|
@@ -88,6 +88,49 @@ function describeError(err) {
|
|
|
88
88
|
}
|
|
89
89
|
return lines.join('\n');
|
|
90
90
|
}
|
|
91
|
+
/** fetch rejects only when no response came back, so any non-abort, non-timeout rejection is `network`. */
|
|
92
|
+
function networkError(err) {
|
|
93
|
+
if (err instanceof TheoremError || isAbortError(err) || isTimeoutError(err)) {
|
|
94
|
+
return err;
|
|
95
|
+
}
|
|
96
|
+
return new TheoremError('network', describeError(err), { cause: err });
|
|
97
|
+
}
|
|
98
|
+
/** `response` with a body whose read that fails is named as `networkError` names it. */
|
|
99
|
+
function bodyOrNetworkError(response) {
|
|
100
|
+
if (!response.body)
|
|
101
|
+
return response;
|
|
102
|
+
const reader = response.body.getReader();
|
|
103
|
+
const body = new ReadableStream({
|
|
104
|
+
async pull(controller) {
|
|
105
|
+
try {
|
|
106
|
+
const next = await reader.read();
|
|
107
|
+
if (next.done)
|
|
108
|
+
controller.close();
|
|
109
|
+
else
|
|
110
|
+
controller.enqueue(next.value);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
controller.error(networkError(err));
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
cancel: (reason) => reader.cancel(reason),
|
|
117
|
+
}, { highWaterMark: 0 });
|
|
118
|
+
const named = new Response(body, response);
|
|
119
|
+
Object.defineProperties(named, {
|
|
120
|
+
url: { value: response.url },
|
|
121
|
+
redirected: { value: response.redirected },
|
|
122
|
+
});
|
|
123
|
+
return named;
|
|
124
|
+
}
|
|
125
|
+
/** `send`, with a failed send or body read named as `networkError` names it. */
|
|
126
|
+
async function sendOrNetworkError(send, ...args) {
|
|
127
|
+
try {
|
|
128
|
+
return bodyOrNetworkError(await send(...args));
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
throw networkError(err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
91
134
|
/** No user wording yet: `withPublicLabels` adds it where the event reaches the host, the one place that knows the profile. */
|
|
92
135
|
function toErrorEvent(err) {
|
|
93
136
|
return {
|
|
@@ -112,4 +155,4 @@ function withPublicLabels(event, labels) {
|
|
|
112
155
|
return event;
|
|
113
156
|
}
|
|
114
157
|
export { ERROR_KINDS } from './theorem-error.js';
|
|
115
|
-
export { describeError, errorCopyText, errorKind, isAbortError, isTimeoutError, kindOfHttpStatus, publicError, TheoremError, throwIfAborted, toErrorEvent, withPublicLabels, };
|
|
158
|
+
export { describeError, errorCopyText, errorKind, isAbortError, isTimeoutError, kindOfHttpStatus, networkError, publicError, sendOrNetworkError, TheoremError, throwIfAborted, toErrorEvent, withPublicLabels, };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TheoremError } from './error.js';
|
|
1
|
+
import { sendOrNetworkError, TheoremError } from './error.js';
|
|
2
2
|
/**
|
|
3
3
|
* IPv4 ranges refused outright:
|
|
4
4
|
* - Loopback: 127.0.0.0/8
|
|
@@ -265,7 +265,7 @@ export async function fetchGuarded(url, init, options) {
|
|
|
265
265
|
sent.set(name, value);
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
-
const response = await fetchFn
|
|
268
|
+
const response = await sendOrNetworkError(fetchFn, target.href, {
|
|
269
269
|
...init,
|
|
270
270
|
method,
|
|
271
271
|
headers: sent,
|
|
@@ -305,7 +305,7 @@ export function dnsOverHttpsResolver(options) {
|
|
|
305
305
|
const url = new URL(options.endpoint);
|
|
306
306
|
url.searchParams.set('name', hostname);
|
|
307
307
|
url.searchParams.set('type', String(type));
|
|
308
|
-
const response = await fetchFn
|
|
308
|
+
const response = await sendOrNetworkError(fetchFn, url.href, {
|
|
309
309
|
headers: { accept: 'application/dns-json' },
|
|
310
310
|
...(signal ? { signal } : {}),
|
|
311
311
|
});
|
|
@@ -5,16 +5,26 @@ import type { KernelRegistry } from '../registry/kernel-registry.js';
|
|
|
5
5
|
import type { DecisionProfile, DecisionRequest, DecisionResult } from '../types.js';
|
|
6
6
|
/** Each decision failure code, and the error kind it reads as. */
|
|
7
7
|
declare const DECISION_ERROR_KINDS: {
|
|
8
|
+
readonly invalid_config: "config";
|
|
8
9
|
readonly invalid_request: "request";
|
|
10
|
+
readonly invalid_input: "input";
|
|
11
|
+
readonly not_allowed: "action";
|
|
9
12
|
readonly authentication: "auth";
|
|
10
13
|
readonly permission: "auth";
|
|
11
14
|
readonly rate_limited: "rate_limit";
|
|
15
|
+
readonly busy: "busy";
|
|
16
|
+
readonly unsupported: "unsupported";
|
|
12
17
|
readonly unavailable: "unavailable";
|
|
13
18
|
readonly network: "network";
|
|
14
19
|
readonly timeout: "timeout";
|
|
15
20
|
readonly cancelled: "cancelled";
|
|
16
21
|
readonly malformed_response: "bad_response";
|
|
22
|
+
readonly refused: "safety";
|
|
23
|
+
readonly blocked: "blocked";
|
|
17
24
|
readonly disclosure_blocked: "blocked";
|
|
25
|
+
readonly declined: "declined";
|
|
26
|
+
readonly failed: "failed";
|
|
27
|
+
readonly internal: "internal";
|
|
18
28
|
};
|
|
19
29
|
/**
|
|
20
30
|
* A normalized native-decision failure; response bodies are deliberately omitted. What the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TheoremError, throwIfAborted } from '../../guardrails/error.js';
|
|
1
|
+
import { errorKind, TheoremError, throwIfAborted } from '../../guardrails/error.js';
|
|
2
2
|
import { labelText } from '../../guardrails/labels.js';
|
|
3
3
|
import { traceJson } from '../../observability/trace-span.js';
|
|
4
4
|
import { guardedCall } from '../provider-runtime.js';
|
|
@@ -8,16 +8,47 @@ import { tracedCall } from './call-trace.js';
|
|
|
8
8
|
import { referencedAssets } from './decision-references.js';
|
|
9
9
|
/** Each decision failure code, and the error kind it reads as. */
|
|
10
10
|
const DECISION_ERROR_KINDS = {
|
|
11
|
+
invalid_config: 'config',
|
|
11
12
|
invalid_request: 'request',
|
|
13
|
+
invalid_input: 'input',
|
|
14
|
+
not_allowed: 'action',
|
|
12
15
|
authentication: 'auth',
|
|
13
16
|
permission: 'auth',
|
|
14
17
|
rate_limited: 'rate_limit',
|
|
18
|
+
busy: 'busy',
|
|
19
|
+
unsupported: 'unsupported',
|
|
15
20
|
unavailable: 'unavailable',
|
|
16
21
|
network: 'network',
|
|
17
22
|
timeout: 'timeout',
|
|
18
23
|
cancelled: 'cancelled',
|
|
19
24
|
malformed_response: 'bad_response',
|
|
25
|
+
refused: 'safety',
|
|
26
|
+
blocked: 'blocked',
|
|
20
27
|
disclosure_blocked: 'blocked',
|
|
28
|
+
declined: 'declined',
|
|
29
|
+
failed: 'failed',
|
|
30
|
+
internal: 'internal',
|
|
31
|
+
};
|
|
32
|
+
/** Each kind's code: a thrown failure keeps its kind. */
|
|
33
|
+
const DECISION_CODES = {
|
|
34
|
+
config: 'invalid_config',
|
|
35
|
+
request: 'invalid_request',
|
|
36
|
+
input: 'invalid_input',
|
|
37
|
+
action: 'not_allowed',
|
|
38
|
+
auth: 'authentication',
|
|
39
|
+
rate_limit: 'rate_limited',
|
|
40
|
+
busy: 'busy',
|
|
41
|
+
unsupported: 'unsupported',
|
|
42
|
+
unavailable: 'unavailable',
|
|
43
|
+
bad_response: 'malformed_response',
|
|
44
|
+
network: 'network',
|
|
45
|
+
timeout: 'timeout',
|
|
46
|
+
safety: 'refused',
|
|
47
|
+
blocked: 'blocked',
|
|
48
|
+
declined: 'declined',
|
|
49
|
+
failed: 'failed',
|
|
50
|
+
cancelled: 'cancelled',
|
|
51
|
+
internal: 'internal',
|
|
21
52
|
};
|
|
22
53
|
/**
|
|
23
54
|
* A normalized native-decision failure; response bodies are deliberately omitted. What the
|
|
@@ -363,25 +394,10 @@ function recordResult(root, result) {
|
|
|
363
394
|
function decisionProviderError(error) {
|
|
364
395
|
if (error instanceof DecisionError)
|
|
365
396
|
return error;
|
|
366
|
-
const kind = error
|
|
367
|
-
? error.kind
|
|
368
|
-
: error instanceof TypeError
|
|
369
|
-
? 'network'
|
|
370
|
-
: 'bad_response';
|
|
371
|
-
const codes = {
|
|
372
|
-
auth: 'authentication',
|
|
373
|
-
rate_limit: 'rate_limited',
|
|
374
|
-
network: 'network',
|
|
375
|
-
busy: 'unavailable',
|
|
376
|
-
unavailable: 'unavailable',
|
|
377
|
-
timeout: 'timeout',
|
|
378
|
-
cancelled: 'cancelled',
|
|
379
|
-
request: 'invalid_request',
|
|
380
|
-
unsupported: 'invalid_request',
|
|
381
|
-
};
|
|
397
|
+
const kind = errorKind(error);
|
|
382
398
|
const code = kind === 'auth' && isRecord(error) && error.status === 403
|
|
383
399
|
? 'permission'
|
|
384
|
-
:
|
|
400
|
+
: DECISION_CODES[kind];
|
|
385
401
|
return new DecisionError(code,
|
|
386
402
|
// why: A framework failure's message holds no response body, and says what stopped the decision.
|
|
387
403
|
error instanceof TheoremError ? error.message : labelText('provider.decision_failed'), isRecord(error) && typeof error.status === 'number' ? error.status : undefined, { cause: error });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TheoremError, throwIfAborted } from '../../guardrails/error.js';
|
|
2
2
|
import { labelText } from '../../guardrails/labels.js';
|
|
3
3
|
import { jsonObjectSchema } from '../json.js';
|
|
4
|
-
import {
|
|
4
|
+
import { operationBroken, operationWireValue, toolInvocationHash, toolOperationOutcomeSchema, } from './operation.js';
|
|
5
5
|
class ReplayedOperation {
|
|
6
6
|
outcome;
|
|
7
7
|
constructor(outcome) {
|
|
@@ -41,7 +41,7 @@ class CoordinatedExecution {
|
|
|
41
41
|
if (result.kind === 'running' || result.kind === 'uncertain')
|
|
42
42
|
throw uncertainError();
|
|
43
43
|
if (result.kind !== 'acquired' || typeof result.claimToken !== 'string' || !result.claimToken)
|
|
44
|
-
throw
|
|
44
|
+
throw operationBroken("The tool coordinator's begin returned no claim"); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
45
45
|
this.claim = { ...operation, claimToken: result.claimToken };
|
|
46
46
|
throwIfAborted(this.signal);
|
|
47
47
|
return this.claim;
|
|
@@ -64,7 +64,7 @@ class CoordinatedExecution {
|
|
|
64
64
|
return undefined;
|
|
65
65
|
}
|
|
66
66
|
if (!this.terminal)
|
|
67
|
-
throw
|
|
67
|
+
throw operationBroken('The tool run ended with no terminal event'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
68
68
|
const { modelResult, outputRaw, failure, awaiting, denied, callNotStarted, aborted, pendingInject, } = settlement;
|
|
69
69
|
const effects = this.claim ? (failure ? 'unknown' : 'completed') : 'not_started';
|
|
70
70
|
const outcome = {
|
|
@@ -96,7 +96,7 @@ class CoordinatedExecution {
|
|
|
96
96
|
if (saved.event.tool.operationId !== operationId ||
|
|
97
97
|
saved.event.tool.callId !== callId ||
|
|
98
98
|
saved.event.tool.name !== toolId)
|
|
99
|
-
throw
|
|
99
|
+
throw operationBroken("The tool coordinator's saved outcome is another operation's"); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
100
100
|
this.committed = true;
|
|
101
101
|
return saved;
|
|
102
102
|
}
|
|
@@ -122,7 +122,7 @@ export async function* coordinateTool(coordinator, identity, invocation, signal,
|
|
|
122
122
|
throw uncertainError();
|
|
123
123
|
if (existing?.kind === 'settled') {
|
|
124
124
|
if (!authorizeReplay)
|
|
125
|
-
throw
|
|
125
|
+
throw operationBroken('A settled tool operation has no replay check'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
126
126
|
const saved = execution.replay(new ReplayedOperation(existing.outcome));
|
|
127
127
|
await authorizeReplay(saved);
|
|
128
128
|
throwIfAborted(signal);
|
|
@@ -146,7 +146,7 @@ export async function* coordinateTool(coordinator, identity, invocation, signal,
|
|
|
146
146
|
catch (error) {
|
|
147
147
|
if (error instanceof ReplayedOperation) {
|
|
148
148
|
if (!authorizeReplay)
|
|
149
|
-
throw
|
|
149
|
+
throw operationBroken('A settled tool operation has no replay check'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
150
150
|
const saved = execution.replay(error);
|
|
151
151
|
await authorizeReplay(saved);
|
|
152
152
|
yield saved.event;
|
|
@@ -16,7 +16,7 @@ import { dispatchToolBody, failedActivity, failedActivityOf, failureEvent, messa
|
|
|
16
16
|
import { HandlerEvents } from './handler-events.js';
|
|
17
17
|
import { fetchToolImages } from './image-fetch.js';
|
|
18
18
|
import { formatToolFailureForModel, formatToolResult, frameToolFailure } from './model-text.js';
|
|
19
|
-
import {
|
|
19
|
+
import { operationBroken, operationWireValue } from './operation.js';
|
|
20
20
|
import { checkPermission, gateDetails, isGateResumeDenied, isGateResumeGranted, isResumeContinuation, } from './permission.js';
|
|
21
21
|
import { executeHttpTool, executeMcpTool, failureWithoutSecret, modelResultFromOutput, omitSecret, outcomeFromUnauth, parseToolOutput, refusedCredentialOutcome, resolveToolAuth, withMedia, } from './remote.js';
|
|
22
22
|
import { extractLoadedIds, profileToolAllow, profileToolsSpec, promoteLoadedTools, } from './resolve.js';
|
|
@@ -467,13 +467,14 @@ async function* receivePageAnswer(tool, ctx, claim, base) {
|
|
|
467
467
|
const execution = ctx.pageExecution;
|
|
468
468
|
if (tool.answeredBy !== 'page' || !execution)
|
|
469
469
|
return ctx;
|
|
470
|
-
if (!claim?.invocation)
|
|
471
|
-
throw
|
|
470
|
+
if (!claim?.invocation) {
|
|
471
|
+
throw new TheoremError('config', `Page tool '${tool.name}' needs a tool execution coordinator (execution)`);
|
|
472
|
+
}
|
|
472
473
|
const argumentsForPage = jsonObjectSchema.parse(operationWireValue(claim.invocation.preparedInput));
|
|
473
474
|
const dispatch = await execution.dispatch(claim, ctx.signal);
|
|
474
475
|
throwIfAborted(ctx.signal);
|
|
475
476
|
if (!dispatch.grantId)
|
|
476
|
-
throw
|
|
477
|
+
throw operationBroken('The page dispatch returned no grantId'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
477
478
|
yield toolEvent(base, {
|
|
478
479
|
phase: 'dispatch',
|
|
479
480
|
grantId: dispatch.grantId,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TheoremError } from '../../guardrails/error.js';
|
|
2
|
+
import { operationBroken, } from './operation.js';
|
|
2
3
|
/** How many operations a memory coordinator tracks before it refuses new ones. */
|
|
3
4
|
const DEFAULT_MEMORY_EXECUTION_LIMIT = 256;
|
|
4
5
|
/**
|
|
@@ -23,6 +24,8 @@ export function createMemoryToolExecution(options = {}) {
|
|
|
23
24
|
const record = records.get(keyOf(claim));
|
|
24
25
|
return record?.state === 'running' && record.claimToken === claim.claimToken;
|
|
25
26
|
};
|
|
27
|
+
const full = () => new TheoremError('config', `The memory tool coordinator holds its maxOperations of ${limit}`);
|
|
28
|
+
const notHeld = () => operationBroken('The memory tool coordinator holds no running claim for this token'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
26
29
|
return {
|
|
27
30
|
lookup: (operation) => Promise.resolve(known(operation)),
|
|
28
31
|
begin: (operation) => {
|
|
@@ -30,28 +33,29 @@ export function createMemoryToolExecution(options = {}) {
|
|
|
30
33
|
if (earlier)
|
|
31
34
|
return Promise.resolve(earlier);
|
|
32
35
|
if (records.size >= limit)
|
|
33
|
-
return Promise.reject(
|
|
36
|
+
return Promise.reject(full());
|
|
34
37
|
const claimToken = crypto.randomUUID();
|
|
35
38
|
records.set(keyOf(operation), { state: 'running', claimToken });
|
|
36
39
|
return Promise.resolve({ kind: 'acquired', claimToken });
|
|
37
40
|
},
|
|
38
41
|
settle: (claim, outcome) => {
|
|
39
42
|
if (!holds(claim))
|
|
40
|
-
return Promise.reject(
|
|
43
|
+
return Promise.reject(notHeld());
|
|
41
44
|
records.set(keyOf(claim), { state: 'settled', outcome });
|
|
42
45
|
return Promise.resolve();
|
|
43
46
|
},
|
|
44
47
|
settleNotStarted: (operation, outcome) => {
|
|
45
|
-
if (records.has(keyOf(operation)))
|
|
46
|
-
return Promise.reject(
|
|
48
|
+
if (records.has(keyOf(operation))) {
|
|
49
|
+
return Promise.reject(operationBroken('The memory tool coordinator already has a record for this operation'));
|
|
50
|
+
}
|
|
47
51
|
if (records.size >= limit)
|
|
48
|
-
return Promise.reject(
|
|
52
|
+
return Promise.reject(full());
|
|
49
53
|
records.set(keyOf(operation), { state: 'settled', outcome });
|
|
50
54
|
return Promise.resolve();
|
|
51
55
|
},
|
|
52
56
|
markUncertain: (claim) => {
|
|
53
57
|
if (!holds(claim))
|
|
54
|
-
return Promise.reject(
|
|
58
|
+
return Promise.reject(notHeld());
|
|
55
59
|
records.set(keyOf(claim), { state: 'uncertain' });
|
|
56
60
|
return Promise.resolve();
|
|
57
61
|
},
|
|
@@ -67,7 +67,8 @@ export interface ToolExecutionCoordinator {
|
|
|
67
67
|
settleNotStarted(operation: ToolOperationIdentity, outcome: ToolOperationOutcome): Promise<void>;
|
|
68
68
|
markUncertain(claim: ToolOperationClaim, reason: ToolOperationUncertainty): Promise<void>;
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
/** A coordinator, a page dispatch or the kernel broke the tool-operation contract. */
|
|
71
|
+
export declare function operationBroken(reason: string): TheoremError;
|
|
71
72
|
export declare function toolInvocationHash(value: unknown): Promise<string>;
|
|
72
73
|
export declare const toolOperationOutcomeSchema: z.ZodType<ToolOperationOutcome>;
|
|
73
74
|
export declare function operationWireValue(value: unknown): JsonValue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { TheoremError } from '../../guardrails/error.js';
|
|
3
3
|
import { errorKindSchema, guardrailHit, provenance } from '../../guardrails/event-schemas.js';
|
|
4
4
|
import { jsonObjectSchema, jsonValueSchema } from '../json.js';
|
|
5
5
|
import { interactionPartSchema, TURN_EVENT_SCHEMAS, turnHistoryMessageSchema, } from '../turn-events.js';
|
|
@@ -7,10 +7,9 @@ export const savedToolInvocationSchema = z.strictObject({
|
|
|
7
7
|
accepted: jsonObjectSchema,
|
|
8
8
|
preparedInput: jsonValueSchema,
|
|
9
9
|
});
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return error;
|
|
10
|
+
/** A coordinator, a page dispatch or the kernel broke the tool-operation contract. */
|
|
11
|
+
export function operationBroken(reason) {
|
|
12
|
+
return new TheoremError('internal', reason);
|
|
14
13
|
}
|
|
15
14
|
export async function toolInvocationHash(value) {
|
|
16
15
|
const ordered = (item) => {
|
|
@@ -78,20 +77,23 @@ export function operationWireValue(value) {
|
|
|
78
77
|
if (!item || typeof item !== 'object')
|
|
79
78
|
return item;
|
|
80
79
|
if (ancestors.has(item))
|
|
81
|
-
throw
|
|
80
|
+
throw operationBroken('Tool operation value has a cycle'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
82
81
|
const prototype = Object.getPrototypeOf(item);
|
|
83
82
|
if (!Array.isArray(item) && prototype !== Object.prototype && prototype !== null) {
|
|
84
|
-
throw
|
|
83
|
+
throw operationBroken('Tool operation value holds an object that is not plain data'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
84
|
+
}
|
|
85
|
+
if (Object.hasOwn(item, '__proto__')) {
|
|
86
|
+
throw operationBroken('Tool operation value has an own __proto__ key'); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
85
87
|
}
|
|
86
|
-
if (Object.hasOwn(item, '__proto__'))
|
|
87
|
-
throw operationUnavailable();
|
|
88
88
|
ancestors.add(item);
|
|
89
89
|
try {
|
|
90
90
|
if (Array.isArray(item))
|
|
91
91
|
return item.map(normalize);
|
|
92
92
|
const entries = Object.entries(Object.getOwnPropertyDescriptors(item));
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
const accessor = entries.find(([, descriptor]) => descriptor.get || descriptor.set);
|
|
94
|
+
if (accessor) {
|
|
95
|
+
throw operationBroken(`Tool operation value has a getter or setter at '${accessor[0]}'`); // labels-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
96
|
+
}
|
|
95
97
|
return Object.fromEntries(entries
|
|
96
98
|
.filter(([, descriptor]) => descriptor.enumerable && descriptor.value !== undefined)
|
|
97
99
|
.map(([key, descriptor]) => [key, normalize(descriptor.value)]));
|
|
@@ -382,13 +382,20 @@ async function* remoteAuthAndPreBody(args) {
|
|
|
382
382
|
...(authRes.audience ? { audience: authRes.audience } : {}),
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
|
+
/** A thrown request's code: a kind with a code of its own, else `request_failed`. */
|
|
386
|
+
const THROWN_CODES = {
|
|
387
|
+
network: 'network_error',
|
|
388
|
+
cancelled: 'cancelled',
|
|
389
|
+
timeout: 'timeout',
|
|
390
|
+
};
|
|
385
391
|
function* thrownOutcome(err, checks) {
|
|
386
392
|
if (errorKind(err) === 'blocked') {
|
|
387
393
|
const blocked = networkBlockedEvent();
|
|
388
394
|
checks.record(blocked);
|
|
389
395
|
return failureOutcome(yield* networkBlocked(err, blocked), false);
|
|
390
396
|
}
|
|
391
|
-
|
|
397
|
+
const kind = errorKind(err);
|
|
398
|
+
return failureOutcome({ code: THROWN_CODES[kind] ?? 'request_failed', kind, message: messageOf(err) }, false);
|
|
392
399
|
}
|
|
393
400
|
const OMIT_CREDENTIAL = '[omitted - credential]';
|
|
394
401
|
/** The most of an error or unparseable body quoted back in a failure message. */
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { TheoremError } from '../../guardrails/error.js';
|
|
2
|
-
import { networkError } from './upstream-tap.js';
|
|
1
|
+
import { networkError, TheoremError } from '../../guardrails/error.js';
|
|
3
2
|
const BACKOFF_MS = [1000, 2000, 4000, 8000];
|
|
4
3
|
/** All the waiting one call may do between tries; a wait that would pass it sends the last answer back. */
|
|
5
4
|
export const WAIT_BUDGET_MS = 5000;
|
|
@@ -3,6 +3,4 @@ export declare function tapeHeaderValue(key: string, value: string): string;
|
|
|
3
3
|
export declare function tapeHeaders(headers?: HeadersInit): Record<string, string>;
|
|
4
4
|
export declare function throwRow(err: unknown): Record<string, unknown>;
|
|
5
5
|
export declare function tapFetch(tap: ModelRequest['tapUpstream'], send?: typeof fetch, keySlot?: KeySlot): typeof fetch;
|
|
6
|
-
/** fetch rejects only when no response came back, so any non-abort, non-timeout rejection is `network`. */
|
|
7
|
-
export declare function networkError(err: unknown): unknown;
|
|
8
6
|
export declare function networkFetch(send?: typeof fetch): typeof fetch;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sendOrNetworkError } from '../../guardrails/error.js';
|
|
2
2
|
const SECRET_HEADER = /key|auth|cookie|secret|token/i;
|
|
3
3
|
export function tapeHeaderValue(key, value) {
|
|
4
4
|
if (SECRET_HEADER.test(key)) {
|
|
@@ -66,20 +66,6 @@ export function tapFetch(tap, send = fetch, keySlot) {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
/** fetch rejects only when no response came back, so any non-abort, non-timeout rejection is `network`. */
|
|
70
|
-
export function networkError(err) {
|
|
71
|
-
if (err instanceof TheoremError || isAbortError(err) || isTimeoutError(err)) {
|
|
72
|
-
return err;
|
|
73
|
-
}
|
|
74
|
-
return new TheoremError('network', describeError(err), { cause: err });
|
|
75
|
-
}
|
|
76
69
|
export function networkFetch(send = fetch) {
|
|
77
|
-
return
|
|
78
|
-
try {
|
|
79
|
-
return await send(url, init);
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
throw networkError(err);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
70
|
+
return (url, init) => sendOrNetworkError(send, url, init);
|
|
85
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/agents",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.528.g44c76f9",
|
|
4
4
|
"description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|