@dogpile/sdk 0.5.0 → 0.6.0
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/CHANGELOG.md +27 -0
- package/dist/browser/index.js +3992 -4997
- package/dist/browser/index.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +5 -1
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/broadcast.d.ts +1 -0
- package/dist/runtime/broadcast.d.ts.map +1 -1
- package/dist/runtime/broadcast.js +132 -69
- package/dist/runtime/broadcast.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +4 -2
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +114 -39
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/defaults.d.ts.map +1 -1
- package/dist/runtime/defaults.js +2 -1
- package/dist/runtime/defaults.js.map +1 -1
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +54 -34
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/model.d.ts.map +1 -1
- package/dist/runtime/model.js +6 -3
- package/dist/runtime/model.js.map +1 -1
- package/dist/runtime/redaction.d.ts +13 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +278 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/sanitization.d.ts +4 -0
- package/dist/runtime/sanitization.d.ts.map +1 -0
- package/dist/runtime/sanitization.js +63 -0
- package/dist/runtime/sanitization.js.map +1 -0
- package/dist/runtime/shared.d.ts +1 -0
- package/dist/runtime/shared.d.ts.map +1 -1
- package/dist/runtime/shared.js +128 -65
- package/dist/runtime/shared.js.map +1 -1
- package/dist/runtime/tools/built-in.d.ts +2 -0
- package/dist/runtime/tools/built-in.d.ts.map +1 -1
- package/dist/runtime/tools/built-in.js +153 -15
- package/dist/runtime/tools/built-in.js.map +1 -1
- package/dist/runtime/tools.d.ts.map +1 -1
- package/dist/runtime/tools.js +29 -7
- package/dist/runtime/tools.js.map +1 -1
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/runtime/validation.js +3 -0
- package/dist/runtime/validation.js.map +1 -1
- package/dist/types/events.d.ts +3 -3
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +3 -1
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -1
- package/src/providers/openai-compatible.ts +5 -1
- package/src/runtime/broadcast.ts +156 -72
- package/src/runtime/coordinator.ts +143 -47
- package/src/runtime/defaults.ts +2 -1
- package/src/runtime/engine.ts +77 -40
- package/src/runtime/model.ts +6 -3
- package/src/runtime/redaction.ts +355 -0
- package/src/runtime/sanitization.ts +81 -0
- package/src/runtime/shared.ts +152 -68
- package/src/runtime/tools/built-in.ts +168 -15
- package/src/runtime/tools.ts +39 -8
- package/src/runtime/validation.ts +3 -0
- package/src/types/events.ts +3 -3
- package/src/types/replay.ts +3 -1
- package/src/types.ts +20 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentDecision,
|
|
3
|
+
DelegateAgentDecision,
|
|
4
|
+
JsonObject,
|
|
5
|
+
ModelRequest,
|
|
6
|
+
ModelResponse,
|
|
7
|
+
ReplayTraceProtocolDecision,
|
|
8
|
+
ReplayTraceProviderCall,
|
|
9
|
+
RunEvent,
|
|
10
|
+
RunEventLog,
|
|
11
|
+
RunResult,
|
|
12
|
+
RuntimeToolExecutionRequest,
|
|
13
|
+
RuntimeToolResult,
|
|
14
|
+
Trace,
|
|
15
|
+
TranscriptEntry,
|
|
16
|
+
TranscriptToolCall
|
|
17
|
+
} from "../types.js";
|
|
18
|
+
|
|
19
|
+
export interface TraceRedactionOptions {
|
|
20
|
+
readonly replacementText?: string;
|
|
21
|
+
readonly redactPrompts?: boolean;
|
|
22
|
+
readonly redactOutputs?: boolean;
|
|
23
|
+
readonly redactToolInputs?: boolean;
|
|
24
|
+
readonly redactToolOutputs?: boolean;
|
|
25
|
+
readonly redactProviderResponses?: boolean;
|
|
26
|
+
readonly redactEmbeddedChildTraces?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const DEFAULT_REPLACEMENT_TEXT = "[REDACTED]";
|
|
30
|
+
const REDACTED_JSON_OBJECT: JsonObject = { redacted: true };
|
|
31
|
+
|
|
32
|
+
export function redactTrace(trace: Trace, options: TraceRedactionOptions = {}): Trace {
|
|
33
|
+
return {
|
|
34
|
+
...trace,
|
|
35
|
+
inputs: shouldRedactPrompts(options)
|
|
36
|
+
? {
|
|
37
|
+
...trace.inputs,
|
|
38
|
+
intent: replacement(options)
|
|
39
|
+
}
|
|
40
|
+
: trace.inputs,
|
|
41
|
+
protocolDecisions: trace.protocolDecisions.map((decision) => redactProtocolDecision(decision, options)),
|
|
42
|
+
providerCalls: trace.providerCalls.map((call) => redactProviderCall(call, options)),
|
|
43
|
+
finalOutput: shouldRedactOutputs(options)
|
|
44
|
+
? {
|
|
45
|
+
...trace.finalOutput,
|
|
46
|
+
output: replacement(options)
|
|
47
|
+
}
|
|
48
|
+
: trace.finalOutput,
|
|
49
|
+
events: trace.events.map((event) => redactRunEvent(event, options)),
|
|
50
|
+
transcript: trace.transcript.map((entry) => redactTranscriptEntry(entry, options))
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function redactRunResult(result: RunResult, options: TraceRedactionOptions = {}): RunResult {
|
|
55
|
+
const trace = redactTrace(result.trace, options);
|
|
56
|
+
const eventLog: RunEventLog = {
|
|
57
|
+
...result.eventLog,
|
|
58
|
+
events: trace.events,
|
|
59
|
+
eventTypes: trace.events.map((event) => event.type),
|
|
60
|
+
eventCount: trace.events.length
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
...result,
|
|
65
|
+
output: shouldRedactOutputs(options) ? replacement(options) : result.output,
|
|
66
|
+
eventLog,
|
|
67
|
+
trace,
|
|
68
|
+
transcript: trace.transcript
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function redactRunEvent(event: RunEvent, options: TraceRedactionOptions): RunEvent {
|
|
73
|
+
switch (event.type) {
|
|
74
|
+
case "model-request":
|
|
75
|
+
return {
|
|
76
|
+
...event,
|
|
77
|
+
request: redactModelRequest(event.request, options)
|
|
78
|
+
};
|
|
79
|
+
case "model-response":
|
|
80
|
+
return {
|
|
81
|
+
...event,
|
|
82
|
+
response: redactModelResponse(event.response, options)
|
|
83
|
+
};
|
|
84
|
+
case "model-output-chunk":
|
|
85
|
+
return shouldRedactOutputs(options)
|
|
86
|
+
? {
|
|
87
|
+
...event,
|
|
88
|
+
text: replacement(options),
|
|
89
|
+
outputLength: 0
|
|
90
|
+
}
|
|
91
|
+
: event;
|
|
92
|
+
case "tool-call":
|
|
93
|
+
return shouldRedactToolInputs(options)
|
|
94
|
+
? {
|
|
95
|
+
...event,
|
|
96
|
+
input: REDACTED_JSON_OBJECT
|
|
97
|
+
}
|
|
98
|
+
: event;
|
|
99
|
+
case "tool-result":
|
|
100
|
+
return {
|
|
101
|
+
...event,
|
|
102
|
+
result: redactRuntimeToolResult(event.result, options)
|
|
103
|
+
};
|
|
104
|
+
case "agent-turn":
|
|
105
|
+
return {
|
|
106
|
+
...event,
|
|
107
|
+
...(shouldRedactPrompts(options) ? { input: replacement(options) } : {}),
|
|
108
|
+
...(shouldRedactOutputs(options) ? { output: replacement(options) } : {}),
|
|
109
|
+
...(event.decision !== undefined ? { decision: redactDecision(event.decision, options) } : {})
|
|
110
|
+
};
|
|
111
|
+
case "broadcast":
|
|
112
|
+
return {
|
|
113
|
+
...event,
|
|
114
|
+
contributions: event.contributions.map((contribution) => ({
|
|
115
|
+
...contribution,
|
|
116
|
+
...(shouldRedactOutputs(options) ? { output: replacement(options) } : {}),
|
|
117
|
+
...(contribution.decision !== undefined
|
|
118
|
+
? { decision: redactDecision(contribution.decision, options) }
|
|
119
|
+
: {})
|
|
120
|
+
}))
|
|
121
|
+
};
|
|
122
|
+
case "sub-run-completed":
|
|
123
|
+
return shouldRedactEmbeddedChildTraces(options)
|
|
124
|
+
? {
|
|
125
|
+
...event,
|
|
126
|
+
subResult: redactRunResult(event.subResult, options)
|
|
127
|
+
}
|
|
128
|
+
: event;
|
|
129
|
+
case "sub-run-failed":
|
|
130
|
+
return shouldRedactEmbeddedChildTraces(options)
|
|
131
|
+
? {
|
|
132
|
+
...event,
|
|
133
|
+
error: {
|
|
134
|
+
...event.error,
|
|
135
|
+
...(event.error.detail !== undefined
|
|
136
|
+
? { detail: redactErrorDetail(event.error.detail, options) }
|
|
137
|
+
: {})
|
|
138
|
+
},
|
|
139
|
+
partialTrace: redactTrace(event.partialTrace, options)
|
|
140
|
+
}
|
|
141
|
+
: event;
|
|
142
|
+
case "final":
|
|
143
|
+
return shouldRedactOutputs(options)
|
|
144
|
+
? {
|
|
145
|
+
...event,
|
|
146
|
+
output: replacement(options)
|
|
147
|
+
}
|
|
148
|
+
: event;
|
|
149
|
+
case "role-assignment":
|
|
150
|
+
case "budget-stop":
|
|
151
|
+
case "sub-run-started":
|
|
152
|
+
case "sub-run-parent-aborted":
|
|
153
|
+
case "sub-run-budget-clamped":
|
|
154
|
+
case "sub-run-queued":
|
|
155
|
+
case "sub-run-concurrency-clamped":
|
|
156
|
+
return event;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function redactProviderCall(
|
|
161
|
+
call: ReplayTraceProviderCall,
|
|
162
|
+
options: TraceRedactionOptions
|
|
163
|
+
): ReplayTraceProviderCall {
|
|
164
|
+
return {
|
|
165
|
+
...call,
|
|
166
|
+
request: redactModelRequest(call.request, options),
|
|
167
|
+
response: redactModelResponse(call.response, options)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function redactProtocolDecision(
|
|
172
|
+
decision: ReplayTraceProtocolDecision,
|
|
173
|
+
options: TraceRedactionOptions
|
|
174
|
+
): ReplayTraceProtocolDecision {
|
|
175
|
+
return {
|
|
176
|
+
...decision,
|
|
177
|
+
...(decision.input !== undefined && shouldRedactPrompts(options) ? { input: replacement(options) } : {}),
|
|
178
|
+
...(decision.output !== undefined && shouldRedactOutputs(options) ? { output: replacement(options) } : {})
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function redactModelRequest(request: ModelRequest, options: TraceRedactionOptions): ModelRequest {
|
|
183
|
+
return shouldRedactPrompts(options)
|
|
184
|
+
? {
|
|
185
|
+
...request,
|
|
186
|
+
messages: request.messages.map((message) => ({
|
|
187
|
+
...message,
|
|
188
|
+
content: replacement(options)
|
|
189
|
+
}))
|
|
190
|
+
}
|
|
191
|
+
: request;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function redactModelResponse(response: ModelResponse, options: TraceRedactionOptions): ModelResponse {
|
|
195
|
+
const redactedToolRequests = response.toolRequests?.map((request) => redactToolRequest(request, options));
|
|
196
|
+
|
|
197
|
+
if (shouldRedactProviderResponses(options)) {
|
|
198
|
+
return {
|
|
199
|
+
text: replacement(options),
|
|
200
|
+
...(response.finishReason !== undefined ? { finishReason: response.finishReason } : {}),
|
|
201
|
+
...(redactedToolRequests !== undefined && redactedToolRequests.length > 0 ? { toolRequests: redactedToolRequests } : {}),
|
|
202
|
+
...(response.usage !== undefined ? { usage: response.usage } : {}),
|
|
203
|
+
...(response.costUsd !== undefined ? { costUsd: response.costUsd } : {})
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
...response,
|
|
209
|
+
...(shouldRedactOutputs(options) ? { text: replacement(options) } : {}),
|
|
210
|
+
...(redactedToolRequests !== undefined ? { toolRequests: redactedToolRequests } : {})
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function redactToolRequest(
|
|
215
|
+
request: RuntimeToolExecutionRequest,
|
|
216
|
+
options: TraceRedactionOptions
|
|
217
|
+
): RuntimeToolExecutionRequest {
|
|
218
|
+
return shouldRedactToolInputs(options)
|
|
219
|
+
? {
|
|
220
|
+
...request,
|
|
221
|
+
input: REDACTED_JSON_OBJECT
|
|
222
|
+
}
|
|
223
|
+
: request;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function redactTranscriptEntry(entry: TranscriptEntry, options: TraceRedactionOptions): TranscriptEntry {
|
|
227
|
+
return {
|
|
228
|
+
...entry,
|
|
229
|
+
...(shouldRedactPrompts(options) ? { input: replacement(options) } : {}),
|
|
230
|
+
...(shouldRedactOutputs(options) ? { output: replacement(options) } : {}),
|
|
231
|
+
...(entry.decision !== undefined ? { decision: redactDecision(entry.decision, options) } : {}),
|
|
232
|
+
...(entry.toolCalls !== undefined
|
|
233
|
+
? { toolCalls: entry.toolCalls.map((toolCall) => redactTranscriptToolCall(toolCall, options)) }
|
|
234
|
+
: {})
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function redactTranscriptToolCall(
|
|
239
|
+
toolCall: TranscriptToolCall,
|
|
240
|
+
options: TraceRedactionOptions
|
|
241
|
+
): TranscriptToolCall {
|
|
242
|
+
return {
|
|
243
|
+
...toolCall,
|
|
244
|
+
...(shouldRedactToolInputs(options) ? { input: REDACTED_JSON_OBJECT } : {}),
|
|
245
|
+
result: redactRuntimeToolResult(toolCall.result, options)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function redactRuntimeToolResult(result: RuntimeToolResult, options: TraceRedactionOptions): RuntimeToolResult {
|
|
250
|
+
if (!shouldRedactToolOutputs(options)) {
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (result.type === "success") {
|
|
255
|
+
return {
|
|
256
|
+
...result,
|
|
257
|
+
output: REDACTED_JSON_OBJECT
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
...result,
|
|
263
|
+
error: {
|
|
264
|
+
...result.error,
|
|
265
|
+
...(result.error.detail !== undefined ? { detail: REDACTED_JSON_OBJECT } : {})
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function redactDecision(
|
|
271
|
+
decision: AgentDecision | readonly DelegateAgentDecision[],
|
|
272
|
+
options: TraceRedactionOptions
|
|
273
|
+
): AgentDecision | readonly DelegateAgentDecision[] {
|
|
274
|
+
if (isDelegateDecisionArray(decision)) {
|
|
275
|
+
return decision.map((delegate) => redactDelegateDecision(delegate, options));
|
|
276
|
+
}
|
|
277
|
+
if (decision.type === "delegate") {
|
|
278
|
+
return redactDelegateDecision(decision, options);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
...decision,
|
|
282
|
+
...(shouldRedactOutputs(options)
|
|
283
|
+
? {
|
|
284
|
+
rationale: replacement(options),
|
|
285
|
+
contribution: replacement(options)
|
|
286
|
+
}
|
|
287
|
+
: {})
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function isDelegateDecisionArray(
|
|
292
|
+
decision: AgentDecision | readonly DelegateAgentDecision[]
|
|
293
|
+
): decision is readonly DelegateAgentDecision[] {
|
|
294
|
+
return Array.isArray(decision);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function redactDelegateDecision(
|
|
298
|
+
decision: DelegateAgentDecision,
|
|
299
|
+
options: TraceRedactionOptions
|
|
300
|
+
): DelegateAgentDecision {
|
|
301
|
+
return shouldRedactPrompts(options)
|
|
302
|
+
? {
|
|
303
|
+
...decision,
|
|
304
|
+
intent: replacement(options)
|
|
305
|
+
}
|
|
306
|
+
: decision;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function redactErrorDetail(detail: JsonObject, options: TraceRedactionOptions): JsonObject {
|
|
310
|
+
const failedDecision = detail["failedDecision"];
|
|
311
|
+
if (
|
|
312
|
+
!shouldRedactPrompts(options) ||
|
|
313
|
+
typeof failedDecision !== "object" ||
|
|
314
|
+
failedDecision === null ||
|
|
315
|
+
Array.isArray(failedDecision)
|
|
316
|
+
) {
|
|
317
|
+
return detail;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
...detail,
|
|
322
|
+
failedDecision: {
|
|
323
|
+
...failedDecision,
|
|
324
|
+
intent: replacement(options)
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function shouldRedactPrompts(options: TraceRedactionOptions): boolean {
|
|
330
|
+
return options.redactPrompts ?? true;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function shouldRedactOutputs(options: TraceRedactionOptions): boolean {
|
|
334
|
+
return options.redactOutputs ?? true;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function shouldRedactToolInputs(options: TraceRedactionOptions): boolean {
|
|
338
|
+
return options.redactToolInputs ?? true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function shouldRedactToolOutputs(options: TraceRedactionOptions): boolean {
|
|
342
|
+
return options.redactToolOutputs ?? true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function shouldRedactProviderResponses(options: TraceRedactionOptions): boolean {
|
|
346
|
+
return options.redactProviderResponses ?? true;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function shouldRedactEmbeddedChildTraces(options: TraceRedactionOptions): boolean {
|
|
350
|
+
return options.redactEmbeddedChildTraces ?? true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function replacement(options: TraceRedactionOptions): string {
|
|
354
|
+
return options.replacementText ?? DEFAULT_REPLACEMENT_TEXT;
|
|
355
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from "../types.js";
|
|
2
|
+
|
|
3
|
+
const safeResponseHeaderNames = new Set([
|
|
4
|
+
"content-type",
|
|
5
|
+
"date",
|
|
6
|
+
"request-id",
|
|
7
|
+
"retry-after",
|
|
8
|
+
"x-ratelimit-limit",
|
|
9
|
+
"x-ratelimit-remaining",
|
|
10
|
+
"x-ratelimit-reset",
|
|
11
|
+
"x-request-id",
|
|
12
|
+
"x-stream-request-id"
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
const authLikeKeyPattern =
|
|
16
|
+
/^(?:authorization|proxy-authorization|cookie|set-cookie|x-api-key|api-key|apikey|api_key|x-auth-token|x-access-token|x-goog-api-key)$/i;
|
|
17
|
+
|
|
18
|
+
export function sanitizeProviderJsonValue(value: unknown): JsonValue | undefined {
|
|
19
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value === "number") {
|
|
24
|
+
return Number.isFinite(value) ? value : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value instanceof Date) {
|
|
28
|
+
return value.toISOString();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return value.map((child) => sanitizeProviderJsonValue(child) ?? null);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isRecord(value)) {
|
|
36
|
+
const result: Record<string, JsonValue> = {};
|
|
37
|
+
|
|
38
|
+
for (const [key, child] of Object.entries(value)) {
|
|
39
|
+
if (isAuthLikeKey(key)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const jsonValue = sanitizeProviderJsonValue(child);
|
|
43
|
+
if (jsonValue !== undefined) {
|
|
44
|
+
result[key] = jsonValue;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function sanitizeProviderResponseHeaders(headers: Headers | Record<string, unknown> | undefined): JsonObject | undefined {
|
|
55
|
+
if (headers === undefined) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const result: Record<string, JsonValue> = {};
|
|
60
|
+
const entries =
|
|
61
|
+
typeof Headers !== "undefined" && headers instanceof Headers
|
|
62
|
+
? Array.from(headers.entries())
|
|
63
|
+
: Object.entries(headers).map(([key, value]) => [key, String(value)] as const);
|
|
64
|
+
|
|
65
|
+
for (const [key, value] of entries) {
|
|
66
|
+
const normalizedKey = key.toLowerCase();
|
|
67
|
+
if (safeResponseHeaderNames.has(normalizedKey) && value !== undefined) {
|
|
68
|
+
result[normalizedKey] = String(value);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isAuthLikeKey(key: string): boolean {
|
|
76
|
+
return authLikeKeyPattern.test(key);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
80
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
81
|
+
}
|
package/src/runtime/shared.ts
CHANGED
|
@@ -56,9 +56,12 @@ interface SharedRunOptions {
|
|
|
56
56
|
readonly signal?: AbortSignal;
|
|
57
57
|
readonly terminate?: TerminationCondition;
|
|
58
58
|
readonly wrapUpHint?: DogpileOptions["wrapUpHint"];
|
|
59
|
+
readonly maxConcurrentAgentTurns?: number;
|
|
59
60
|
readonly emit?: (event: RunEvent) => void;
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const DEFAULT_MAX_CONCURRENT_AGENT_TURNS = 4;
|
|
64
|
+
|
|
62
65
|
export async function runShared(options: SharedRunOptions): Promise<RunResult> {
|
|
63
66
|
const runId = createRunId();
|
|
64
67
|
const events: RunEvent[] = [];
|
|
@@ -124,77 +127,88 @@ export async function runShared(options: SharedRunOptions): Promise<RunResult> {
|
|
|
124
127
|
|
|
125
128
|
if (!stopIfNeeded()) {
|
|
126
129
|
const providerCallSlots: ReplayTraceProviderCall[] = [];
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
130
|
+
const fanout = createFanoutAbortController(options.signal);
|
|
131
|
+
const turnResults = await (async () => {
|
|
132
|
+
try {
|
|
133
|
+
return await mapWithConcurrency(
|
|
134
|
+
activeAgents,
|
|
135
|
+
options.maxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS,
|
|
136
|
+
fanout,
|
|
137
|
+
async (agent, index) => {
|
|
138
|
+
throwIfAborted(fanout.signal, options.model.id);
|
|
139
|
+
const turn = index + 1;
|
|
140
|
+
const input = buildSharedInput(options.intent, sharedState, turn);
|
|
141
|
+
const request: ModelRequest = {
|
|
142
|
+
temperature: options.temperature,
|
|
143
|
+
signal: fanout.signal,
|
|
144
|
+
metadata: {
|
|
145
|
+
runId,
|
|
146
|
+
protocol: "shared",
|
|
147
|
+
agentId: agent.id,
|
|
148
|
+
role: agent.role,
|
|
149
|
+
tier: options.tier,
|
|
150
|
+
turn,
|
|
151
|
+
...toolAvailability
|
|
148
152
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
messages: wrapUpHint.inject(
|
|
154
|
+
[
|
|
155
|
+
{
|
|
156
|
+
role: "system",
|
|
157
|
+
content: buildSystemPrompt(agent)
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
role: "user",
|
|
161
|
+
content: input
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
{
|
|
165
|
+
runId,
|
|
166
|
+
protocol: "shared",
|
|
167
|
+
cost: totalCost,
|
|
168
|
+
events,
|
|
169
|
+
transcript,
|
|
170
|
+
iteration: transcript.length,
|
|
171
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
};
|
|
175
|
+
const response = await generateModelTurn({
|
|
176
|
+
model: options.model,
|
|
177
|
+
request,
|
|
155
178
|
runId,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
agent,
|
|
180
|
+
input,
|
|
181
|
+
emit,
|
|
182
|
+
callId: providerCallIdFor(runId, providerCalls.length + index + 1),
|
|
183
|
+
onProviderCall(call): void {
|
|
184
|
+
providerCallSlots[index] = call;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
const decision = parseAgentDecision(response.text);
|
|
188
|
+
const toolCalls = await executeModelResponseToolRequests({
|
|
189
|
+
response,
|
|
190
|
+
executor: toolExecutor,
|
|
191
|
+
agentId: agent.id,
|
|
192
|
+
role: agent.role,
|
|
193
|
+
turn
|
|
194
|
+
});
|
|
195
|
+
throwIfAborted(fanout.signal, options.model.id);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
agent,
|
|
199
|
+
turn,
|
|
200
|
+
input,
|
|
201
|
+
response,
|
|
202
|
+
decision,
|
|
203
|
+
toolCalls,
|
|
204
|
+
turnCost: responseCost(response)
|
|
205
|
+
};
|
|
175
206
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
agentId: agent.id,
|
|
182
|
-
role: agent.role,
|
|
183
|
-
turn
|
|
184
|
-
});
|
|
185
|
-
throwIfAborted(options.signal, options.model.id);
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
agent,
|
|
189
|
-
turn,
|
|
190
|
-
input,
|
|
191
|
-
response,
|
|
192
|
-
decision,
|
|
193
|
-
toolCalls,
|
|
194
|
-
turnCost: responseCost(response)
|
|
195
|
-
};
|
|
196
|
-
})
|
|
197
|
-
);
|
|
207
|
+
);
|
|
208
|
+
} finally {
|
|
209
|
+
fanout.cleanup();
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
198
212
|
providerCalls.push(...providerCallSlots.filter((call): call is ReplayTraceProviderCall => call !== undefined));
|
|
199
213
|
|
|
200
214
|
for (const result of turnResults) {
|
|
@@ -379,3 +393,73 @@ function responseCost(response: ModelResponse): CostSummary {
|
|
|
379
393
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
380
394
|
};
|
|
381
395
|
}
|
|
396
|
+
|
|
397
|
+
interface FanoutAbortController {
|
|
398
|
+
readonly signal: AbortSignal;
|
|
399
|
+
abort(reason: unknown): void;
|
|
400
|
+
cleanup(): void;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function createFanoutAbortController(parentSignal: AbortSignal | undefined): FanoutAbortController {
|
|
404
|
+
const controller = new AbortController();
|
|
405
|
+
let removeParentListener = (): void => {};
|
|
406
|
+
|
|
407
|
+
if (parentSignal?.aborted) {
|
|
408
|
+
controller.abort(parentSignal.reason);
|
|
409
|
+
} else if (parentSignal !== undefined) {
|
|
410
|
+
const abortFromParent = (): void => {
|
|
411
|
+
controller.abort(parentSignal.reason);
|
|
412
|
+
};
|
|
413
|
+
parentSignal.addEventListener("abort", abortFromParent, { once: true });
|
|
414
|
+
removeParentListener = (): void => {
|
|
415
|
+
parentSignal.removeEventListener("abort", abortFromParent);
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
signal: controller.signal,
|
|
421
|
+
abort(reason: unknown): void {
|
|
422
|
+
if (!controller.signal.aborted) {
|
|
423
|
+
controller.abort(reason);
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
cleanup(): void {
|
|
427
|
+
removeParentListener();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function mapWithConcurrency<T, R>(
|
|
433
|
+
items: readonly T[],
|
|
434
|
+
maxConcurrent: number,
|
|
435
|
+
fanout: FanoutAbortController,
|
|
436
|
+
mapper: (item: T, index: number) => Promise<R>
|
|
437
|
+
): Promise<R[]> {
|
|
438
|
+
if (items.length === 0) {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const results: R[] = new Array(items.length);
|
|
443
|
+
let nextIndex = 0;
|
|
444
|
+
let firstError: unknown;
|
|
445
|
+
const workerCount = Math.min(maxConcurrent, items.length);
|
|
446
|
+
|
|
447
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
448
|
+
while (nextIndex < items.length && firstError === undefined) {
|
|
449
|
+
const index = nextIndex;
|
|
450
|
+
nextIndex += 1;
|
|
451
|
+
try {
|
|
452
|
+
results[index] = await mapper(items[index]!, index);
|
|
453
|
+
} catch (error) {
|
|
454
|
+
firstError ??= error;
|
|
455
|
+
fanout.abort(error);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}));
|
|
459
|
+
|
|
460
|
+
if (firstError !== undefined) {
|
|
461
|
+
throw firstError;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return results;
|
|
465
|
+
}
|