@theoremjs/agents 0.3.4-next.484.g20d8b9d → 0.3.4-next.522.g9e684cd
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -51
- package/esm/mod.d.ts +2 -2
- package/esm/mod.js +1 -1
- package/esm/src/guardrails/credential-scan.d.ts +1 -1
- package/esm/src/guardrails/credential-scan.js +41 -18
- package/esm/src/guardrails/destinations.js +6 -2
- package/esm/src/guardrails/detect-at.d.ts +5 -5
- package/esm/src/guardrails/detect-at.js +51 -41
- package/esm/src/guardrails/detect-reply.d.ts +1 -1
- package/esm/src/guardrails/detect-reply.js +4 -2
- package/esm/src/guardrails/egress-automata.js +203 -202
- package/esm/src/guardrails/egress-compiler.js +1 -4
- package/esm/src/guardrails/egress-stream.js +10 -3
- package/esm/src/guardrails/egress-urls.js +4 -2
- package/esm/src/guardrails/error.d.ts +1 -1
- package/esm/src/guardrails/error.js +19 -2
- package/esm/src/guardrails/host-patterns.d.ts +1 -4
- package/esm/src/guardrails/host-patterns.js +1 -10
- package/esm/src/guardrails/injection-patterns.d.ts +3 -1
- package/esm/src/guardrails/injection-patterns.js +87 -4
- package/esm/src/guardrails/labels.d.ts +2 -2
- package/esm/src/guardrails/labels.js +7 -5
- package/esm/src/guardrails/mod.d.ts +2 -2
- package/esm/src/guardrails/mod.js +1 -1
- package/esm/src/guardrails/normalize.js +4 -2
- package/esm/src/guardrails/prompt-echo.js +4 -2
- package/esm/src/guardrails/sanitize.d.ts +1 -1
- package/esm/src/guardrails/sanitize.js +4 -3
- package/esm/src/guardrails/sensitive.d.ts +2 -2
- package/esm/src/guardrails/sensitive.js +1 -1
- package/esm/src/host/client-turn.js +8 -1
- package/esm/src/interface/profile-interface.js +13 -0
- package/esm/src/kernel/answer-tool.d.ts +14 -0
- package/esm/src/kernel/answer-tool.js +26 -0
- package/esm/src/kernel/engine/decision-references.d.ts +14 -0
- package/esm/src/kernel/engine/decision-references.js +55 -0
- package/esm/src/kernel/engine/decision.d.ts +3 -3
- package/esm/src/kernel/engine/decision.js +38 -22
- package/esm/src/kernel/engine/embedding.js +0 -2
- package/esm/src/kernel/engine/runner/gates.js +40 -5
- package/esm/src/kernel/engine/runner/mod.js +13 -5
- package/esm/src/kernel/engine/runner/state.d.ts +9 -1
- package/esm/src/kernel/engine/runner/state.js +10 -1
- package/esm/src/kernel/engine/runner/steps.js +50 -13
- package/esm/src/kernel/engine/runner/stream.d.ts +14 -8
- package/esm/src/kernel/engine/runner/stream.js +25 -13
- package/esm/src/kernel/engine/turn-trace.js +2 -2
- package/esm/src/kernel/mod.d.ts +1 -1
- package/esm/src/kernel/mod.js +1 -1
- package/esm/src/kernel/profile-presence.js +12 -0
- package/esm/src/kernel/profile-scope.js +14 -14
- package/esm/src/kernel/protocols/binding.d.ts +3 -1
- package/esm/src/kernel/protocols/binding.js +6 -0
- package/esm/src/kernel/protocols/limits.d.ts +10 -11
- package/esm/src/kernel/protocols/limits.js +9 -23
- package/esm/src/kernel/protocols/runtime.d.ts +3 -2
- package/esm/src/kernel/protocols/runtime.js +3 -4
- package/esm/src/kernel/provider-budget.d.ts +2 -2
- package/esm/src/kernel/provider-budget.js +22 -12
- package/esm/src/kernel/provider-runtime.js +8 -5
- package/esm/src/kernel/registry/profiles.js +3 -3
- package/esm/src/kernel/registry/resolve.js +4 -0
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/schema.js +28 -2
- package/esm/src/kernel/tools/execute.js +3 -1
- package/esm/src/kernel/tools/image-fetch.d.ts +2 -4
- package/esm/src/kernel/tools/image-fetch.js +19 -20
- package/esm/src/kernel/tools/mcp-sessions.d.ts +7 -2
- package/esm/src/kernel/tools/mcp-sessions.js +21 -12
- package/esm/src/kernel/tools/registry.js +21 -0
- package/esm/src/kernel/tools/remote.d.ts +0 -2
- package/esm/src/kernel/tools/remote.js +28 -23
- package/esm/src/kernel/tools/types.d.ts +30 -4
- package/esm/src/kernel/turn-events.d.ts +2 -0
- package/esm/src/kernel/turn-events.js +1 -0
- package/esm/src/kernel/types.d.ts +4 -2
- package/esm/src/observability/spans.js +7 -8
- package/esm/src/observability/trace-catalog.js +1 -0
- package/esm/src/providers/embedding-limits.js +4 -4
- package/esm/src/providers/gemini/{embedding-protocol.d.ts → embeddings-protocol.d.ts} +3 -3
- package/esm/src/providers/gemini/{embedding-protocol.js → embeddings-protocol.js} +3 -3
- package/esm/src/providers/gemini/live-protocol.js +4 -1
- package/esm/src/providers/gemini/provider.js +2 -2
- package/esm/src/providers/local/embeddings-protocol.d.ts +7 -0
- package/esm/src/providers/local/{embedding-protocol.js → embeddings-protocol.js} +2 -2
- package/esm/src/providers/local/provider.js +2 -2
- package/esm/src/providers/mod.d.ts +3 -3
- package/esm/src/providers/mod.js +3 -3
- package/esm/src/providers/openrouter/decisions-protocol.js +11 -8
- package/esm/src/providers/openrouter/embeddings-protocol.d.ts +6 -0
- package/esm/src/providers/openrouter/{embedding-protocol.js → embeddings-protocol.js} +2 -2
- package/esm/src/providers/openrouter/provider.js +2 -2
- package/package.json +1 -1
- package/esm/src/providers/local/embedding-protocol.d.ts +0 -7
- package/esm/src/providers/openrouter/embedding-protocol.d.ts +0 -6
package/esm/src/kernel/mod.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { outOfScopeFields, profileTypesForField } from './profile-scope.js';
|
|
|
29
29
|
export type { DecisionModelBinding, ModelBinding, RuntimeAbilities } from './protocols/binding.js';
|
|
30
30
|
export { checkBinding, commonModelSettingsSchema, decisionModelBindingSchema, keySlotSchema, keySlotsOf, modelBindingSchema, } from './protocols/binding.js';
|
|
31
31
|
export type { ProviderLimits } from './protocols/limits.js';
|
|
32
|
-
export { providerLimitsSchema
|
|
32
|
+
export { providerLimitsSchema } from './protocols/limits.js';
|
|
33
33
|
export type { EstimatedCost, ModelPrice, PriceTable, Rates } from './protocols/prices.js';
|
|
34
34
|
export { costFromPrices } from './protocols/prices.js';
|
|
35
35
|
export type { Continuation, DecisionCall, EmbeddingCall, EmbeddingMediaKind, EmbeddingModelLimits, ExtensionBodyAt, ExtensionReader, LiveToolResult, ModelProfileType, Protocol, ProtocolContentEvent, ProtocolContext, ProtocolDefinition, ProtocolEvent, ProtocolExtension, ProtocolOperations, ProtocolRequest, ProtocolSession, ProtocolSocket, SendInit, UpstreamFailure, } from './protocols/protocol.js';
|
package/esm/src/kernel/mod.js
CHANGED
|
@@ -18,7 +18,7 @@ export { PROFILE_GRAPH, profileGraphFacet, spineFacetsForProfileType, } from './
|
|
|
18
18
|
export { unsetValue } from './profile-presence.js';
|
|
19
19
|
export { outOfScopeFields, profileTypesForField } from './profile-scope.js';
|
|
20
20
|
export { checkBinding, commonModelSettingsSchema, decisionModelBindingSchema, keySlotSchema, keySlotsOf, modelBindingSchema, } from './protocols/binding.js';
|
|
21
|
-
export { providerLimitsSchema
|
|
21
|
+
export { providerLimitsSchema } from './protocols/limits.js';
|
|
22
22
|
export { costFromPrices } from './protocols/prices.js';
|
|
23
23
|
export { carriesTo, defineProtocol, extendProtocol, modelOptions } from './protocols/protocol.js';
|
|
24
24
|
export { bearer, createProviderRegistry, defineProvider, endpointUrlSchema, header, query, } from './protocols/provider.js';
|
|
@@ -40,6 +40,18 @@ export const PROFILE_FIELD_PRESENCE = {
|
|
|
40
40
|
},
|
|
41
41
|
'models.*.allowEffortSelect': { unset: 'Off', value: false },
|
|
42
42
|
'models.*.timeoutMs': { unset: 'No timeout' },
|
|
43
|
+
'models.*.limits': { unset: 'No limits' },
|
|
44
|
+
...Object.fromEntries([
|
|
45
|
+
'operationTimeoutMs',
|
|
46
|
+
'connectTimeoutMs',
|
|
47
|
+
'liveStepTimeoutMs',
|
|
48
|
+
'maxWireRecordBytes',
|
|
49
|
+
'maxWireResponseBytes',
|
|
50
|
+
'maxEventBytes',
|
|
51
|
+
'maxStepBytes',
|
|
52
|
+
'maxStepEvents',
|
|
53
|
+
'maxPendingToolCalls',
|
|
54
|
+
].map((field) => [`models.*.limits.${field}`, { unset: 'No limit' }])),
|
|
43
55
|
'models.*.compaction': { unset: 'Off' },
|
|
44
56
|
'models.*.compaction.maxTokens': { required: true },
|
|
45
57
|
'models.*.compaction.compactAt': { required: true },
|
|
@@ -25,10 +25,10 @@ const MODEL_PROFILE_TYPES = [
|
|
|
25
25
|
const TURN_TYPES = ['text', 'image', 'speech', 'live'];
|
|
26
26
|
/** Types whose `inputs` are turn inputs (text, files, slots) rather than decision state. */
|
|
27
27
|
const TURN_INPUT_TYPES = ['text', 'image'];
|
|
28
|
-
const TURN_INPUT_REASON = 'a decision takes JSON state,
|
|
28
|
+
const TURN_INPUT_REASON = 'a decision takes JSON state, and live takes its input through live.ingress';
|
|
29
29
|
/** Types that take slots and context: the turn types, and a call. */
|
|
30
30
|
const SLOT_TYPES = ['text', 'image', 'live'];
|
|
31
|
-
const SLOT_REASON = 'a decision takes JSON state, and
|
|
31
|
+
const SLOT_REASON = 'a decision takes JSON state, and an embedding call embeds its input alone';
|
|
32
32
|
/** Guardrails a host profile may set keep `host`; the rest guard a model turn. */
|
|
33
33
|
function turnGuardrailTypes(key) {
|
|
34
34
|
return HOST_GUARDRAIL_FIELDS.includes(key)
|
|
@@ -79,11 +79,11 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
79
79
|
},
|
|
80
80
|
'identity.system': {
|
|
81
81
|
profileTypes: ['text', 'image', 'live'],
|
|
82
|
-
reason: 'speech has no system channel (the input text is the transcript)
|
|
82
|
+
reason: 'speech has no system channel (the input text is the transcript), a decision is asked only through its questions, and an embedding call embeds its input alone',
|
|
83
83
|
},
|
|
84
84
|
'identity.systemByRole': {
|
|
85
85
|
profileTypes: ['text', 'image', 'live'],
|
|
86
|
-
reason: 'speech has no system channel (the input text is the transcript)
|
|
86
|
+
reason: 'speech has no system channel (the input text is the transcript), a decision is asked only through its questions, and an embedding call embeds its input alone',
|
|
87
87
|
},
|
|
88
88
|
models: { profileTypes: MODEL_PROFILE_TYPES, reason: 'a host profile runs no model' },
|
|
89
89
|
'models.*.protocol': {
|
|
@@ -96,7 +96,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
96
96
|
},
|
|
97
97
|
'models.*.compaction': {
|
|
98
98
|
profileTypes: ['text', 'image', 'speech'],
|
|
99
|
-
reason: 'live compacts with live.contextCompression',
|
|
99
|
+
reason: 'live compacts with live.contextCompression, and decision and embedding calls keep no conversation to compact',
|
|
100
100
|
},
|
|
101
101
|
'profile.defaultModel': {
|
|
102
102
|
profileTypes: [...TURN_TYPES, 'embedding'],
|
|
@@ -116,7 +116,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
116
116
|
live: { profileTypes: ['live'], reason: 'only live profiles open a realtime session' },
|
|
117
117
|
tools: {
|
|
118
118
|
profileTypes: ['text', 'image', 'live', 'host'],
|
|
119
|
-
reason: 'speech and
|
|
119
|
+
reason: 'speech, decision, and embedding profiles call no tools',
|
|
120
120
|
},
|
|
121
121
|
'tools.t1Policy': {
|
|
122
122
|
profileTypes: ['text', 'image'],
|
|
@@ -132,7 +132,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
132
132
|
},
|
|
133
133
|
'inputs.voice': {
|
|
134
134
|
profileTypes: ['text'],
|
|
135
|
-
reason: 'no image model reads audio,
|
|
135
|
+
reason: 'no image model reads audio, live takes audio through live.ingress, a decision takes JSON state, and an embedding call takes text and files',
|
|
136
136
|
},
|
|
137
137
|
'inputs.text': {
|
|
138
138
|
profileTypes: [...TURN_INPUT_TYPES, 'embedding'],
|
|
@@ -176,7 +176,7 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
176
176
|
},
|
|
177
177
|
outputs: {
|
|
178
178
|
profileTypes: ['text', 'image', 'speech'],
|
|
179
|
-
reason: 'live output is the realtime session, and decision and host profiles produce no turn output',
|
|
179
|
+
reason: 'live output is the realtime session, and decision, embedding, and host profiles produce no turn output',
|
|
180
180
|
},
|
|
181
181
|
'outputs.structured': {
|
|
182
182
|
profileTypes: ['text'],
|
|
@@ -205,27 +205,27 @@ export const PROFILE_FIELD_SCOPE = {
|
|
|
205
205
|
},
|
|
206
206
|
'guardrails.quota': {
|
|
207
207
|
profileTypes: turnGuardrailTypes('quota'),
|
|
208
|
-
reason: 'quota counts model turns',
|
|
208
|
+
reason: 'quota counts model turns, and decision, embedding, and host profiles run none',
|
|
209
209
|
},
|
|
210
210
|
'guardrails.detect': {
|
|
211
211
|
profileTypes: turnGuardrailTypes('detect'),
|
|
212
|
-
reason: '
|
|
212
|
+
reason: 'decision and embedding calls run none of the turn guardrails',
|
|
213
213
|
},
|
|
214
214
|
'guardrails.blockedReply': {
|
|
215
215
|
profileTypes: ['text', 'image', 'live'],
|
|
216
|
-
reason: 'only a reply can be blocked, and speech, decision, and host profiles write none',
|
|
216
|
+
reason: 'only a reply can be blocked, and speech, decision, embedding, and host profiles write none',
|
|
217
217
|
},
|
|
218
218
|
'guardrails.network': {
|
|
219
219
|
profileTypes: ['text', 'image', 'live', 'host'],
|
|
220
|
-
reason: 'speech and
|
|
220
|
+
reason: 'speech, decision, and embedding profiles call no tools',
|
|
221
221
|
},
|
|
222
222
|
'guardrails.taint': {
|
|
223
223
|
profileTypes: ['text', 'image', 'live'],
|
|
224
|
-
reason: 'speech and
|
|
224
|
+
reason: 'speech, decision, and embedding profiles call no tools, and a host profile runs no turn to taint',
|
|
225
225
|
},
|
|
226
226
|
'guardrails.disclosure': {
|
|
227
227
|
profileTypes: ['decision'],
|
|
228
|
-
reason: '
|
|
228
|
+
reason: 'only a decision profile sends state to its model, and disclosure gates that state',
|
|
229
229
|
},
|
|
230
230
|
};
|
|
231
231
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { type JsonObject } from '../json.js';
|
|
3
3
|
import { type KeySlot } from '../schema.js';
|
|
4
|
+
import { type ProviderLimits } from './limits.js';
|
|
4
5
|
import { type ModelProfileType, type Protocol } from './protocol.js';
|
|
5
6
|
import type { RegisteredProvider } from './provider.js';
|
|
6
7
|
/** A vault slot name. */
|
|
@@ -31,9 +32,10 @@ export interface ModelBinding extends z.infer<typeof commonModelSettingsSchema>
|
|
|
31
32
|
keySlot?: string;
|
|
32
33
|
fallbackKeySlot?: string;
|
|
33
34
|
options?: JsonObject;
|
|
35
|
+
limits?: ProviderLimits;
|
|
34
36
|
}
|
|
35
37
|
/** A decision profile's model: no generation settings, and a time limit. */
|
|
36
|
-
export interface DecisionModelBinding extends Pick<ModelBinding, 'protocol' | 'provider' | 'apiId' | 'keySlot' | 'fallbackKeySlot' | 'options'> {
|
|
38
|
+
export interface DecisionModelBinding extends Pick<ModelBinding, 'protocol' | 'provider' | 'apiId' | 'keySlot' | 'fallbackKeySlot' | 'options' | 'limits'> {
|
|
37
39
|
timeoutMs?: number;
|
|
38
40
|
}
|
|
39
41
|
export declare const modelBindingSchema: z.ZodType<ModelBinding>;
|
|
@@ -3,6 +3,7 @@ import { TheoremError } from '../../guardrails/error.js';
|
|
|
3
3
|
import { labelText } from '../../guardrails/labels.js';
|
|
4
4
|
import { jsonObjectSchema } from '../json.js';
|
|
5
5
|
import { KEY_SLOT_NAME } from '../schema.js';
|
|
6
|
+
import { providerLimitsSchema } from './limits.js';
|
|
6
7
|
import { modelOptions } from './protocol.js';
|
|
7
8
|
/** A vault slot name. */
|
|
8
9
|
export const keySlotSchema = z.string().regex(KEY_SLOT_NAME);
|
|
@@ -61,6 +62,10 @@ const modelBinding = z.strictObject({
|
|
|
61
62
|
.optional()
|
|
62
63
|
// labels-exempt: builder field description, read by the studio and docs
|
|
63
64
|
.describe("What this model's protocol takes beyond the shared settings."),
|
|
65
|
+
limits: providerLimitsSchema
|
|
66
|
+
.optional()
|
|
67
|
+
// labels-exempt: builder field description, read by the studio and docs
|
|
68
|
+
.describe("Theorem's cut-offs for this model's calls; each one left out is no limit."),
|
|
64
69
|
});
|
|
65
70
|
true;
|
|
66
71
|
export const modelBindingSchema = modelBinding;
|
|
@@ -72,6 +77,7 @@ const decisionModelBinding = modelBinding
|
|
|
72
77
|
keySlot: true,
|
|
73
78
|
fallbackKeySlot: true,
|
|
74
79
|
options: true,
|
|
80
|
+
limits: true,
|
|
75
81
|
})
|
|
76
82
|
.extend({
|
|
77
83
|
timeoutMs: z
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
/** The budgets
|
|
2
|
+
/** The budgets a provider call runs under. Each one left out is no limit: the provider's own applies. */
|
|
3
3
|
export interface ProviderLimits {
|
|
4
|
-
operationTimeoutMs
|
|
5
|
-
connectTimeoutMs
|
|
6
|
-
liveStepTimeoutMs
|
|
7
|
-
maxWireRecordBytes
|
|
8
|
-
maxWireResponseBytes
|
|
9
|
-
maxEventBytes
|
|
10
|
-
maxStepBytes
|
|
11
|
-
maxStepEvents
|
|
12
|
-
maxPendingToolCalls
|
|
4
|
+
operationTimeoutMs?: number;
|
|
5
|
+
connectTimeoutMs?: number;
|
|
6
|
+
liveStepTimeoutMs?: number;
|
|
7
|
+
maxWireRecordBytes?: number;
|
|
8
|
+
maxWireResponseBytes?: number;
|
|
9
|
+
maxEventBytes?: number;
|
|
10
|
+
maxStepBytes?: number;
|
|
11
|
+
maxStepEvents?: number;
|
|
12
|
+
maxPendingToolCalls?: number;
|
|
13
13
|
}
|
|
14
14
|
export declare const providerLimitsSchema: z.ZodType<ProviderLimits>;
|
|
15
|
-
export declare function resolveProviderLimits(overrides?: Partial<ProviderLimits>): ProviderLimits;
|
|
@@ -2,27 +2,13 @@ import { z } from 'zod';
|
|
|
2
2
|
const positiveBudget = z.number().int().positive().max(Number.MAX_SAFE_INTEGER);
|
|
3
3
|
const timerBudget = positiveBudget.max(2147483647);
|
|
4
4
|
export const providerLimitsSchema = z.strictObject({
|
|
5
|
-
operationTimeoutMs: timerBudget,
|
|
6
|
-
connectTimeoutMs: timerBudget,
|
|
7
|
-
liveStepTimeoutMs: timerBudget,
|
|
8
|
-
maxWireRecordBytes: positiveBudget,
|
|
9
|
-
maxWireResponseBytes: positiveBudget,
|
|
10
|
-
maxEventBytes: positiveBudget,
|
|
11
|
-
maxStepBytes: positiveBudget,
|
|
12
|
-
maxStepEvents: positiveBudget,
|
|
13
|
-
maxPendingToolCalls: positiveBudget,
|
|
5
|
+
operationTimeoutMs: timerBudget.optional(),
|
|
6
|
+
connectTimeoutMs: timerBudget.optional(),
|
|
7
|
+
liveStepTimeoutMs: timerBudget.optional(),
|
|
8
|
+
maxWireRecordBytes: positiveBudget.optional(),
|
|
9
|
+
maxWireResponseBytes: positiveBudget.optional(),
|
|
10
|
+
maxEventBytes: positiveBudget.optional(),
|
|
11
|
+
maxStepBytes: positiveBudget.optional(),
|
|
12
|
+
maxStepEvents: positiveBudget.optional(),
|
|
13
|
+
maxPendingToolCalls: positiveBudget.optional(),
|
|
14
14
|
});
|
|
15
|
-
export function resolveProviderLimits(overrides = {}) {
|
|
16
|
-
return Object.freeze(providerLimitsSchema.parse({
|
|
17
|
-
operationTimeoutMs: 120000,
|
|
18
|
-
connectTimeoutMs: 30000,
|
|
19
|
-
liveStepTimeoutMs: 120000,
|
|
20
|
-
maxWireRecordBytes: 16 * 1024 * 1024,
|
|
21
|
-
maxWireResponseBytes: 64 * 1024 * 1024,
|
|
22
|
-
maxEventBytes: 16 * 1024 * 1024,
|
|
23
|
-
maxStepBytes: 64 * 1024 * 1024,
|
|
24
|
-
maxStepEvents: 100000,
|
|
25
|
-
maxPendingToolCalls: 32,
|
|
26
|
-
...overrides,
|
|
27
|
-
}));
|
|
28
|
-
}
|
|
@@ -3,7 +3,7 @@ import { type JsonObject } from '../json.js';
|
|
|
3
3
|
import type { KeyVault } from '../registry/vault.js';
|
|
4
4
|
import type { DecisionResult, KeySlot } from '../types.js';
|
|
5
5
|
import { type DecisionModelBinding, type ModelBinding } from './binding.js';
|
|
6
|
-
import {
|
|
6
|
+
import type { ProviderLimits } from './limits.js';
|
|
7
7
|
import type { DecisionCall, EmbeddingCall, EmbeddingResult, ModelProfileType, ProtocolOperations } from './protocol.js';
|
|
8
8
|
import type { RegisteredProvider } from './provider.js';
|
|
9
9
|
/** Opens a WebSocket with the headers and subprotocols a sign-in put on it. */
|
|
@@ -18,7 +18,8 @@ export type OpenWebSocket = (url: string, init: {
|
|
|
18
18
|
export declare function plainWebSocket(provider: string): OpenWebSocket;
|
|
19
19
|
/** What a host lends every provider call: its keys, its network, its clock, and its budgets. */
|
|
20
20
|
export interface ProviderHostOptions {
|
|
21
|
-
limits
|
|
21
|
+
/** Overrides the model's own `limits`, one field at a time. */
|
|
22
|
+
limits?: ProviderLimits;
|
|
22
23
|
vault?: KeyVault;
|
|
23
24
|
fetch?: typeof fetch;
|
|
24
25
|
wait?: Wait;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { kindOfHttpStatus, TheoremError } from '../../guardrails/error.js';
|
|
1
|
+
import { describeError, kindOfHttpStatus, TheoremError } from '../../guardrails/error.js';
|
|
2
2
|
import { labelText } from '../../guardrails/labels.js';
|
|
3
3
|
import { retryTransient, waitDefault } from '../../providers/shared/retry.js';
|
|
4
4
|
import { readSseChunks } from '../../providers/shared/sse.js';
|
|
@@ -7,7 +7,6 @@ import { KEY_FALLBACK_ROW } from '../../providers/types.js';
|
|
|
7
7
|
import { jsonValueSchema } from '../json.js';
|
|
8
8
|
import { bytesToBase64 } from '../util/base64.js';
|
|
9
9
|
import { bindingServe, keySlotsOf, } from './binding.js';
|
|
10
|
-
import { resolveProviderLimits } from './limits.js';
|
|
11
10
|
import { costFromPrices } from './prices.js';
|
|
12
11
|
/**
|
|
13
12
|
* The runtime's own WebSocket. It takes no headers, so a sign-in that needs them is refused; a host
|
|
@@ -249,7 +248,7 @@ function protocolContext(setup, slot, raw, wire = { sent: false }, asked = []) {
|
|
|
249
248
|
apiId,
|
|
250
249
|
options: setup.binding.options ?? {},
|
|
251
250
|
builtInTools,
|
|
252
|
-
limits: setup.limits ??
|
|
251
|
+
limits: setup.limits ?? setup.binding.limits ?? {},
|
|
253
252
|
...(setup.signal ? { signal: setup.signal } : {}),
|
|
254
253
|
tapUpstream: tap,
|
|
255
254
|
async *send(body, init) {
|
|
@@ -375,7 +374,7 @@ function failureEvent(error) {
|
|
|
375
374
|
type: 'error',
|
|
376
375
|
errorKind: error.kind,
|
|
377
376
|
// invariant: `error` is the person's wording, added from the profile's labels; the upstream text is internal.
|
|
378
|
-
errorInternal: error
|
|
377
|
+
errorInternal: describeError(error),
|
|
379
378
|
...(error.copy ? { errorCopy: error.copy } : {}),
|
|
380
379
|
};
|
|
381
380
|
}
|
|
@@ -11,9 +11,9 @@ export declare class ProviderBudget {
|
|
|
11
11
|
private stepBytes;
|
|
12
12
|
private stepEvents;
|
|
13
13
|
private calls;
|
|
14
|
-
private deadline
|
|
14
|
+
private deadline?;
|
|
15
15
|
private releaseSignal;
|
|
16
|
-
constructor(limits: ProviderLimits, caller?: AbortSignal, timeoutMs?: number);
|
|
16
|
+
constructor(limits: ProviderLimits, caller?: AbortSignal, timeoutMs?: number | undefined);
|
|
17
17
|
resumeOperation(): void;
|
|
18
18
|
/** Milliseconds before the running timer ends the call. */
|
|
19
19
|
timeLeft(): number;
|
|
@@ -21,6 +21,9 @@ function combinedSignal(signals) {
|
|
|
21
21
|
}
|
|
22
22
|
return { signal: controller.signal, dispose };
|
|
23
23
|
}
|
|
24
|
+
function over(value, limit) {
|
|
25
|
+
return limit !== undefined && value > limit;
|
|
26
|
+
}
|
|
24
27
|
export class ProviderBudget {
|
|
25
28
|
limits;
|
|
26
29
|
controller = new AbortController();
|
|
@@ -38,10 +41,14 @@ export class ProviderBudget {
|
|
|
38
41
|
const effective = combinedSignal(caller ? [caller, this.controller.signal] : [this.controller.signal]);
|
|
39
42
|
this.signal = effective.signal;
|
|
40
43
|
this.releaseSignal = effective.dispose;
|
|
44
|
+
if (timeoutMs === undefined)
|
|
45
|
+
return;
|
|
41
46
|
this.startTimer(timeoutMs);
|
|
42
47
|
this.deadline = performance.now() + timeoutMs;
|
|
43
48
|
}
|
|
44
49
|
resumeOperation() {
|
|
50
|
+
if (this.deadline === undefined)
|
|
51
|
+
return;
|
|
45
52
|
const remaining = this.deadline - performance.now();
|
|
46
53
|
if (remaining <= 0) {
|
|
47
54
|
this.controller.abort(new TheoremError('timeout', labelText('error.timeout')));
|
|
@@ -51,7 +58,8 @@ export class ProviderBudget {
|
|
|
51
58
|
}
|
|
52
59
|
/** Milliseconds before the running timer ends the call. */
|
|
53
60
|
timeLeft() {
|
|
54
|
-
|
|
61
|
+
const end = this.timerDeadline ?? this.deadline;
|
|
62
|
+
return end === undefined ? Number.POSITIVE_INFINITY : end - performance.now();
|
|
55
63
|
}
|
|
56
64
|
startTimer(ms) {
|
|
57
65
|
this.stopTimer();
|
|
@@ -73,8 +81,9 @@ export class ProviderBudget {
|
|
|
73
81
|
this.releaseSignal();
|
|
74
82
|
}
|
|
75
83
|
startLiveStep() {
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
const ms = this.limits.liveStepTimeoutMs;
|
|
85
|
+
if (this.timer === undefined && ms !== undefined)
|
|
86
|
+
this.startTimer(ms);
|
|
78
87
|
}
|
|
79
88
|
fail() {
|
|
80
89
|
const error = new TheoremError('bad_response', labelText('provider.budget_exhausted'), {
|
|
@@ -95,9 +104,9 @@ export class ProviderBudget {
|
|
|
95
104
|
this.checkDeadline();
|
|
96
105
|
this.stepBytes += bytes;
|
|
97
106
|
this.stepEvents++;
|
|
98
|
-
if (bytes
|
|
99
|
-
this.stepBytes
|
|
100
|
-
this.stepEvents
|
|
107
|
+
if (over(bytes, this.limits.maxEventBytes) ||
|
|
108
|
+
over(this.stepBytes, this.limits.maxStepBytes) ||
|
|
109
|
+
over(this.stepEvents, this.limits.maxStepEvents))
|
|
101
110
|
this.fail();
|
|
102
111
|
}
|
|
103
112
|
event(event) {
|
|
@@ -106,7 +115,7 @@ export class ProviderBudget {
|
|
|
106
115
|
this.calls.add(event.call.callId);
|
|
107
116
|
if (event.type === 'tool_cancel')
|
|
108
117
|
this.calls.delete(event.callId);
|
|
109
|
-
if (this.calls.size
|
|
118
|
+
if (over(this.calls.size, this.limits.maxPendingToolCalls))
|
|
110
119
|
this.fail();
|
|
111
120
|
}
|
|
112
121
|
resetStep() {
|
|
@@ -138,7 +147,7 @@ export class ProviderBudget {
|
|
|
138
147
|
: data instanceof ArrayBuffer || ArrayBuffer.isView(data)
|
|
139
148
|
? data.byteLength
|
|
140
149
|
: 0;
|
|
141
|
-
if (bytes
|
|
150
|
+
if (!over(bytes, this.limits.maxWireRecordBytes))
|
|
142
151
|
return;
|
|
143
152
|
event.stopImmediatePropagation();
|
|
144
153
|
try {
|
|
@@ -168,7 +177,8 @@ export class ProviderBudget {
|
|
|
168
177
|
}
|
|
169
178
|
const reader = response.body.getReader();
|
|
170
179
|
let responseBytes = 0;
|
|
171
|
-
const record =
|
|
180
|
+
const record = this.limits.maxWireRecordBytes !== undefined &&
|
|
181
|
+
response.headers.get('content-type')?.includes('text/event-stream')
|
|
172
182
|
? new SseRecordBudget(this)
|
|
173
183
|
: undefined;
|
|
174
184
|
const abort = () => {
|
|
@@ -190,8 +200,8 @@ export class ProviderBudget {
|
|
|
190
200
|
}
|
|
191
201
|
responseBytes += next.value.byteLength;
|
|
192
202
|
this.wireBytes += next.value.byteLength;
|
|
193
|
-
if (responseBytes
|
|
194
|
-
this.wireBytes
|
|
203
|
+
if (over(responseBytes, this.limits.maxWireResponseBytes) ||
|
|
204
|
+
over(this.wireBytes, this.limits.maxWireResponseBytes))
|
|
195
205
|
this.fail();
|
|
196
206
|
record?.accept(next.value);
|
|
197
207
|
controller.enqueue(next.value);
|
|
@@ -233,7 +243,7 @@ class SseRecordBudget {
|
|
|
233
243
|
this.emptyCR = false;
|
|
234
244
|
}
|
|
235
245
|
this.recordBytes++;
|
|
236
|
-
if (this.recordBytes
|
|
246
|
+
if (over(this.recordBytes, this.budget.limits.maxWireRecordBytes))
|
|
237
247
|
this.budget.fail();
|
|
238
248
|
if (this.emptyCR) {
|
|
239
249
|
this.recordBytes = 0;
|
|
@@ -5,7 +5,7 @@ import { historyHash, providerCheckpointSchema, } from './checkpoint.js';
|
|
|
5
5
|
import { stepReplyMessages } from './interaction-parts.js';
|
|
6
6
|
import { jsonObjectSchema, jsonValueSchema } from './json.js';
|
|
7
7
|
import { keySlotsOf, protocolOf, } from './protocols/binding.js';
|
|
8
|
-
import {
|
|
8
|
+
import { providerLimitsSchema } from './protocols/limits.js';
|
|
9
9
|
import { carriesTo, } from './protocols/protocol.js';
|
|
10
10
|
import { callProtocol, plainWebSocket } from './protocols/runtime.js';
|
|
11
11
|
import { ProviderBudget } from './provider-budget.js';
|
|
@@ -128,11 +128,14 @@ function diagnosticError(error, clean) {
|
|
|
128
128
|
return new DOMException(message, error.name);
|
|
129
129
|
if (isAbortError(error))
|
|
130
130
|
return new DOMException(message, 'AbortError');
|
|
131
|
+
const cause = error instanceof Error && error.cause !== undefined
|
|
132
|
+
? { cause: diagnosticError(error.cause, clean) }
|
|
133
|
+
: {};
|
|
131
134
|
const result = error instanceof TheoremError
|
|
132
|
-
? new TheoremError(error.kind, message, { copy: diagnosticCopy(error.copy, clean) })
|
|
135
|
+
? new TheoremError(error.kind, message, { copy: diagnosticCopy(error.copy, clean), ...cause })
|
|
133
136
|
: error instanceof TypeError
|
|
134
|
-
? new TypeError(message)
|
|
135
|
-
: new Error(message);
|
|
137
|
+
? new TypeError(message, cause)
|
|
138
|
+
: new Error(message, cause);
|
|
136
139
|
if (isRecord(error) && typeof error.status === 'number')
|
|
137
140
|
Object.assign(result, { status: error.status });
|
|
138
141
|
return result;
|
|
@@ -188,7 +191,7 @@ const SECRET_FIELD = /^(?:authorization|proxy-authorization|cookie|set-cookie|ap
|
|
|
188
191
|
* the host's budgets, with every key it signs with kept out of errors and traces.
|
|
189
192
|
*/
|
|
190
193
|
export function guardedCall(provider, binding, profileType, host, req) {
|
|
191
|
-
const limits =
|
|
194
|
+
const limits = Object.freeze(providerLimitsSchema.parse({ ...binding.limits, ...host.limits }));
|
|
192
195
|
const budget = new ProviderBudget(limits, req.signal, req.timeoutMs ?? (req.liveConnection ? limits.connectTimeoutMs : limits.operationTimeoutMs));
|
|
193
196
|
const secrets = new Set();
|
|
194
197
|
const remember = (value) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TOOL_BOUNDARIES } from '../../guardrails/boundaries.js';
|
|
2
2
|
import { detectProblem } from '../../guardrails/detectors.js';
|
|
3
|
-
import { TheoremError } from '../../guardrails/error.js';
|
|
3
|
+
import { errorKind, TheoremError } from '../../guardrails/error.js';
|
|
4
4
|
import { labelText, validateLabelOverrides } from '../../guardrails/labels.js';
|
|
5
5
|
import { BLOCKED_REPLY_ON_BLOCK, } from '../../guardrails/types.js';
|
|
6
6
|
import { resolveObservabilityPolicy } from '../../observability/resolve-policy.js';
|
|
@@ -50,7 +50,7 @@ function validateDecisionBinding(profileId, modelId, binding) {
|
|
|
50
50
|
if ('retry' in binding) {
|
|
51
51
|
throw new TheoremError('config',
|
|
52
52
|
// labels-exempt: developer contract / internal diagnostic
|
|
53
|
-
`Profile ${profileId} model '${modelId}': decision
|
|
53
|
+
`Profile ${profileId} model '${modelId}': a decision binding takes no retry; a decision retries only a transient failure, by the kernel's rule`);
|
|
54
54
|
}
|
|
55
55
|
if (binding.timeoutMs !== undefined &&
|
|
56
56
|
(!Number.isFinite(binding.timeoutMs) || binding.timeoutMs <= 0)) {
|
|
@@ -332,7 +332,7 @@ function assertObservability(profileId, spec) {
|
|
|
332
332
|
throw err;
|
|
333
333
|
}
|
|
334
334
|
const message = err instanceof Error ? err.message : String(err);
|
|
335
|
-
throw new TheoremError(
|
|
335
|
+
throw new TheoremError(errorKind(err), `Profile ${profileId}: ${message}`, { cause: err });
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
function assertIdentitySystem(input) {
|
|
@@ -4,6 +4,7 @@ import { TheoremError } from '../../guardrails/error.js';
|
|
|
4
4
|
import { resolveGuardrailPolicy } from '../../guardrails/policy.js';
|
|
5
5
|
import { replyIsJudged } from '../../guardrails/progressive-yield.js';
|
|
6
6
|
import { sanitizeTurnRequest } from '../../guardrails/sanitize.js';
|
|
7
|
+
import { ANSWER_TOOL } from '../answer-tool.js';
|
|
7
8
|
import { DEFAULT_MAX_STEPS } from '../profile-presence.js';
|
|
8
9
|
import { profileTurnResumption } from '../stop.js';
|
|
9
10
|
import { projectTools } from '../tools/project.js';
|
|
@@ -171,6 +172,9 @@ function resolveTurnInRegistry(registry, req, model) {
|
|
|
171
172
|
const builtins = toolSnapshot.builtins;
|
|
172
173
|
const structuredId = resolveStructured(profile, input.slots);
|
|
173
174
|
assertOutputMode(profile, structuredId);
|
|
175
|
+
if (structuredId && toolSnapshot.wire.some((tool) => tool.name === ANSWER_TOOL)) {
|
|
176
|
+
throw new TheoremError('config', `Profile ${profile.id}: a structured turn answers through '${ANSWER_TOOL}', so no tool of its own may take that name`);
|
|
177
|
+
}
|
|
174
178
|
assertSpeechRole(profile, safe);
|
|
175
179
|
const builtInArgs = resolveBuiltInArgs(profile, picked, binding, safe.builtInArgs);
|
|
176
180
|
const transport = resolveTransport(profile, binding);
|
|
@@ -5,7 +5,7 @@ function createSchemaRegistry() {
|
|
|
5
5
|
return {
|
|
6
6
|
register(id, spec) {
|
|
7
7
|
if ('enforced' in spec) {
|
|
8
|
-
throw new TheoremError('config', `registerStructured '${id}': enforced was removed; every structured schema is sent to the model as its response format`);
|
|
8
|
+
throw new TheoremError('config', `registerStructured '${id}': enforced was removed; every structured schema is sent to the model, as its response format or as its answer tool's input`);
|
|
9
9
|
}
|
|
10
10
|
if (!spec.jsonSchema ||
|
|
11
11
|
typeof spec.jsonSchema !== 'object' ||
|
package/esm/src/kernel/schema.js
CHANGED
|
@@ -520,6 +520,16 @@ export const PROFILE_FIELDS = withScopeAndPresence({
|
|
|
520
520
|
'providerContinuation.history': field("'host' | 'stateless' | 'provider'", 'Who holds the conversation a checkpoint continues: the host passes history back, the provider keeps it and the checkpoint travels alone, or the profile is stateless and every request stands alone.', ['host', 'stateless', 'provider']),
|
|
521
521
|
'models.*.apiId': field('string', "The model's id as the provider lists it, such as gemini-2.5-flash; sent on every call. The name this model has in the profile is its key in models."),
|
|
522
522
|
'models.*.timeoutMs': field('number', 'How long a decision waits for its provider, in milliseconds. Omit for no timeout.'),
|
|
523
|
+
'models.*.limits': field('ProviderLimits', "The points at which Theorem stops this model's calls. Each one left out is no limit, so the provider's own limits apply. A runTurn limits value overrides each field it sets."),
|
|
524
|
+
'models.*.limits.operationTimeoutMs': field('number', 'How long one call may take, in milliseconds, retries included.'),
|
|
525
|
+
'models.*.limits.connectTimeoutMs': field('number', 'How long a live session may take to open, in milliseconds.'),
|
|
526
|
+
'models.*.limits.liveStepTimeoutMs': field('number', 'How long a live session may wait for the model to answer, in milliseconds.'),
|
|
527
|
+
'models.*.limits.maxWireRecordBytes': field('number', 'The most bytes in one record the provider sends: a stream event or a socket message.'),
|
|
528
|
+
'models.*.limits.maxWireResponseBytes': field('number', 'The most bytes the provider sends for one step.'),
|
|
529
|
+
'models.*.limits.maxEventBytes': field('number', 'The most bytes in one event of a step.'),
|
|
530
|
+
'models.*.limits.maxStepBytes': field('number', 'The most bytes in all the events of one step.'),
|
|
531
|
+
'models.*.limits.maxStepEvents': field('number', 'The most events in one step.'),
|
|
532
|
+
'models.*.limits.maxPendingToolCalls': field('number', 'The most tool calls one step may ask for.'),
|
|
523
533
|
decision: field('{ contract: DecisionContractId }', 'Names the host decision this profile makes.'),
|
|
524
534
|
'decision.contract': field('string', 'Your name for this decision, on its traces. Not sent to the model. Not checked against the questions.'),
|
|
525
535
|
'models.*.efforts': field('Record<string, ThinkingLevel>', 'Named thinking levels. A turn picks one when allowEffortSelect is on. A provider refuses a level its model does not take. Speech ignores them, and so may an image profile without includeText.'),
|
|
@@ -762,12 +772,12 @@ export const EXTRA_FIELDS = {
|
|
|
762
772
|
noul: 'One number, which may weigh named criteria.',
|
|
763
773
|
box: 'Where something is in an image: its top, bottom, left and right edges, with a confidence.',
|
|
764
774
|
}),
|
|
765
|
-
'decision.questions.instructions': field('DecisionEntry', "What to ask about the state. Name a state field in backticks, such as `photo`, and one of the decision's images by its reference, such as <ginger>. A box
|
|
775
|
+
'decision.questions.instructions': field('DecisionEntry', "What to ask about the state. Name a state field in backticks, such as `photo`, and one of the decision's images by its reference, such as <ginger>. A box names what to find and the image it is in, such as the ginger in `photo`."),
|
|
766
776
|
'decision.questions.image': {
|
|
767
777
|
...field('string', "The image a box is in: one of the decision's images, such as <ghs02>, or a state field its instructions name, such as `photo`. The model is told it in the instructions."),
|
|
768
778
|
unset: 'The image the instructions name',
|
|
769
779
|
},
|
|
770
|
-
'decision.images': field('Record<string, DecisionAsset>',
|
|
780
|
+
'decision.images': field('Record<string, DecisionAsset>', "Pictures a decision can send. Write one's reference, such as <ghs02>, in the state or a question, and the model reads the picture there; a picture nothing references is not sent. A PNG or JPEG."),
|
|
771
781
|
'decision.images.name': field('string', 'The reference a question writes, without its angle brackets: ghs02 is written <ghs02>. Lowercase letters, digits and _.'),
|
|
772
782
|
'registerTool.name': field('string', "The tool's id, which profiles list in tools.allow (or a model's builtInTools) and the model calls a custom tool by."),
|
|
773
783
|
'registerTool.description': field('string', 'Tells the model what the tool does (custom tools only).'),
|
|
@@ -788,6 +798,22 @@ export const EXTRA_FIELDS = {
|
|
|
788
798
|
...field("'page'", 'Who answers a call to a function tool. With page, the page the agent is on returns the output, and the tool has no handler.', ['page']),
|
|
789
799
|
unset: 'Its handler',
|
|
790
800
|
},
|
|
801
|
+
'registerTool.maxResponseBytes': {
|
|
802
|
+
...field('number', 'The most bytes of a response body an HTTP or MCP tool reads; past it the call fails with response_too_large.'),
|
|
803
|
+
unset: 'No limit',
|
|
804
|
+
},
|
|
805
|
+
'registerTool.session': {
|
|
806
|
+
...field('McpSessionLimits', "How long and how many of an MCP tool's server sessions the kernel keeps."),
|
|
807
|
+
unset: 'No limits',
|
|
808
|
+
},
|
|
809
|
+
'registerTool.session.idleMs': {
|
|
810
|
+
...field('number', 'How long a session may sit unused before the next call opens a new one, in milliseconds.'),
|
|
811
|
+
unset: 'No limit',
|
|
812
|
+
},
|
|
813
|
+
'registerTool.session.max': {
|
|
814
|
+
...field('number', 'The most sessions the tool keeps open; past it the least recently used is dropped.'),
|
|
815
|
+
unset: 'No limit',
|
|
816
|
+
},
|
|
791
817
|
'registerTool.maxCallsPerTurn': {
|
|
792
818
|
...field('number', 'How many times one turn of the calling agent may run this tool.'),
|
|
793
819
|
unset: 'As many as its steps allow',
|
|
@@ -742,7 +742,9 @@ async function* settleBodyOutcome(args) {
|
|
|
742
742
|
provisional: { outputRaw: outcome.outputRaw, modelResult: outcome.modelResult },
|
|
743
743
|
sources: tool.sources,
|
|
744
744
|
...(tool.type !== 'function' && tool.images
|
|
745
|
-
? {
|
|
745
|
+
? {
|
|
746
|
+
images: (output) => fetchToolImages(tool.images, output, ctx, tool.imageLimits),
|
|
747
|
+
}
|
|
746
748
|
: {}),
|
|
747
749
|
labels: tool.labels,
|
|
748
750
|
reproject: makeReproject((v) => parseToolOutput(tool.output, v),
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import type { InteractionPart, ToolWarning } from '../turn-events.js';
|
|
2
|
-
import type { ToolContext } from './types.js';
|
|
3
|
-
/** The most pictures one call hands the model. */
|
|
4
|
-
export declare const MAX_TOOL_IMAGES = 4;
|
|
2
|
+
import type { ToolContext, ToolImageLimits } from './types.js';
|
|
5
3
|
/**
|
|
6
4
|
* The pictures a tool's `images(output)` names, fetched through the profile's network policy (every
|
|
7
5
|
* hop, redirects included, no tool credentials) and handed to the model as image parts. A picture
|
|
8
6
|
* that is not an https address, blocked, slow, too big or not an image is left out and reported in
|
|
9
7
|
* one `images_unfetched` warning: the call itself stays whole. `urls` are the ones fetched.
|
|
10
8
|
*/
|
|
11
|
-
export declare function fetchToolImages(images: (output: never) => string[], output: unknown, ctx: ToolContext): Promise<{
|
|
9
|
+
export declare function fetchToolImages(images: (output: never) => string[], output: unknown, ctx: ToolContext, limits?: ToolImageLimits): Promise<{
|
|
12
10
|
parts: InteractionPart[];
|
|
13
11
|
urls: string[];
|
|
14
12
|
warning?: ToolWarning;
|