@nifrajs/coding-agent 5.0.0 → 5.1.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/LICENSE +21 -0
- package/README.md +8 -0
- package/dist/approvals.d.ts +30 -2
- package/dist/approvals.d.ts.map +1 -1
- package/dist/approvals.js +43 -12
- package/dist/approvals.js.map +1 -1
- package/dist/extensions.js +4 -2
- package/dist/extensions.js.map +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +35 -24
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/isolated.js +3 -2
- package/dist/isolated.js.map +1 -1
- package/dist/native.d.ts +7 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +11 -8
- package/dist/native.js.map +1 -1
- package/dist/process.d.ts +2 -2
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +15 -3
- package/dist/process.js.map +1 -1
- package/dist/rpc.d.ts +6 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +1 -0
- package/dist/rpc.js.map +1 -1
- package/package.json +6 -6
- package/src/approvals.ts +70 -16
- package/src/extensions.ts +6 -2
- package/src/host.ts +31 -22
- package/src/index.ts +2 -0
- package/src/isolated.ts +3 -2
- package/src/native.ts +19 -13
- package/src/process.ts +15 -3
- package/src/rpc.ts +7 -0
package/src/approvals.ts
CHANGED
|
@@ -21,9 +21,44 @@ export interface ApprovalRequest {
|
|
|
21
21
|
*/
|
|
22
22
|
readonly coordinate?: DecisionCoordinate
|
|
23
23
|
readonly createdAt: number
|
|
24
|
+
/**
|
|
25
|
+
* Absolute epoch-ms after which the approval is denied as timed out. An approval opened with a
|
|
26
|
+
* `"never"` timeout reports {@link APPROVAL_NEVER_EXPIRES_AT}, so `now < expiresAt` keeps treating
|
|
27
|
+
* it as live.
|
|
28
|
+
*/
|
|
24
29
|
readonly expiresAt: number
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* How long an approval may wait: whole milliseconds from 1 ms to 24 h, or `"never"` for no timer. A
|
|
34
|
+
* `"never"` approval still settles on an answer, a cancel, the end of its turn, and close.
|
|
35
|
+
*/
|
|
36
|
+
export type ApprovalTimeout = number | "never"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The `expiresAt` of an approval that never times out: the largest epoch-ms a `Date` holds
|
|
40
|
+
* (+275760-09-13). It survives JSON, unlike `Infinity`, and `new Date(expiresAt)` stays valid.
|
|
41
|
+
*/
|
|
42
|
+
export const APPROVAL_NEVER_EXPIRES_AT = 8_640_000_000_000_000
|
|
43
|
+
|
|
44
|
+
const DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60_000
|
|
45
|
+
const MAX_APPROVAL_TIMEOUT_MS = 24 * 60 * 60_000
|
|
46
|
+
|
|
47
|
+
/** True for `"never"` or a whole number of milliseconds from 1 ms to 24 h. */
|
|
48
|
+
export function isApprovalTimeout(value: unknown): value is ApprovalTimeout {
|
|
49
|
+
return (
|
|
50
|
+
value === "never" ||
|
|
51
|
+
(typeof value === "number" &&
|
|
52
|
+
Number.isSafeInteger(value) &&
|
|
53
|
+
value >= 1 &&
|
|
54
|
+
value <= MAX_APPROVAL_TIMEOUT_MS)
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function approvalExpiresAt(createdAt: number, timeoutMs: ApprovalTimeout): number {
|
|
59
|
+
return timeoutMs === "never" ? APPROVAL_NEVER_EXPIRES_AT : createdAt + timeoutMs
|
|
60
|
+
}
|
|
61
|
+
|
|
27
62
|
/** Stable, content-free reasons a coordinate-matched resolution is refused. */
|
|
28
63
|
export type ApprovalMatchRejection =
|
|
29
64
|
| "unknown_boundary"
|
|
@@ -45,7 +80,11 @@ export interface ApprovalDecision {
|
|
|
45
80
|
|
|
46
81
|
export interface ApprovalManagerOptions {
|
|
47
82
|
readonly maxPending?: number
|
|
48
|
-
|
|
83
|
+
/**
|
|
84
|
+
* How long an approval waits before it is denied as timed out (default 5 minutes, at most 24 h).
|
|
85
|
+
* `"never"` removes the timer; `maxPending` still bounds how many approvals can wait.
|
|
86
|
+
*/
|
|
87
|
+
readonly timeoutMs?: ApprovalTimeout
|
|
49
88
|
readonly onRequired?: (request: ApprovalRequest) => void | PromiseLike<void>
|
|
50
89
|
readonly onResolved?: (decision: ApprovalDecision) => void | PromiseLike<void>
|
|
51
90
|
}
|
|
@@ -55,7 +94,7 @@ interface PendingApproval {
|
|
|
55
94
|
/** Settles with the decision; it exists before `onRequired` runs, so no decision can miss it. */
|
|
56
95
|
readonly decided: Promise<boolean>
|
|
57
96
|
readonly settle: (approved: boolean) => void
|
|
58
|
-
readonly timer: ReturnType<typeof setTimeout>
|
|
97
|
+
readonly timer: ReturnType<typeof setTimeout> | undefined
|
|
59
98
|
}
|
|
60
99
|
|
|
61
100
|
/** The agent protocol's token alphabet, so every protocol-valid id is accepted here too. */
|
|
@@ -77,16 +116,17 @@ export class ApprovalManager {
|
|
|
77
116
|
this.options = {
|
|
78
117
|
...options,
|
|
79
118
|
maxPending: options.maxPending ?? 32,
|
|
80
|
-
timeoutMs: options.timeoutMs ??
|
|
119
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS,
|
|
81
120
|
}
|
|
82
121
|
if (!Number.isSafeInteger(this.options.maxPending) || this.options.maxPending < 1)
|
|
83
122
|
throw new RangeError("approvals: maxPending must be positive")
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
123
|
+
if (!isApprovalTimeout(this.options.timeoutMs))
|
|
124
|
+
throw new RangeError('approvals: timeoutMs must be between 1ms and 24h, or "never"')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The configured timeout, for {@link approvalRequestFromEvent}. */
|
|
128
|
+
get timeoutMs(): ApprovalTimeout {
|
|
129
|
+
return this.options.timeoutMs
|
|
90
130
|
}
|
|
91
131
|
|
|
92
132
|
get pending(): readonly ApprovalRequest[] {
|
|
@@ -219,13 +259,17 @@ export class ApprovalManager {
|
|
|
219
259
|
...(input.reason === undefined ? {} : { reason: input.reason.slice(0, 512) }),
|
|
220
260
|
...(input.coordinate === undefined ? {} : { coordinate: input.coordinate }),
|
|
221
261
|
createdAt,
|
|
222
|
-
expiresAt: createdAt
|
|
262
|
+
expiresAt: approvalExpiresAt(createdAt, this.options.timeoutMs),
|
|
223
263
|
})
|
|
224
264
|
const { promise: decided, resolve: settle } = Promise.withResolvers<boolean>()
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
265
|
+
const timeoutMs = this.options.timeoutMs
|
|
266
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
267
|
+
if (timeoutMs !== "never") {
|
|
268
|
+
timer = setTimeout(() => {
|
|
269
|
+
this.settle(request.id, false, "approval timed out")
|
|
270
|
+
}, timeoutMs)
|
|
271
|
+
;(timer as unknown as { unref?: () => void }).unref?.()
|
|
272
|
+
}
|
|
229
273
|
const pending: PendingApproval = { request, decided, settle, timer }
|
|
230
274
|
this.pendingApprovals.set(request.id, pending)
|
|
231
275
|
await this.options.onRequired?.(request)
|
|
@@ -233,7 +277,17 @@ export class ApprovalManager {
|
|
|
233
277
|
}
|
|
234
278
|
}
|
|
235
279
|
|
|
236
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Project a backend approval event into a request. Pass the broker's timeout
|
|
282
|
+
* (`ApprovalManager.timeoutMs`) so `expiresAt` matches when it will actually be denied; the default
|
|
283
|
+
* is the broker's default of 5 minutes.
|
|
284
|
+
*/
|
|
285
|
+
export function approvalRequestFromEvent(
|
|
286
|
+
event: AgentApprovalRequiredEvent,
|
|
287
|
+
timeoutMs: ApprovalTimeout = DEFAULT_APPROVAL_TIMEOUT_MS,
|
|
288
|
+
): ApprovalRequest {
|
|
289
|
+
if (!isApprovalTimeout(timeoutMs))
|
|
290
|
+
throw new RangeError('approvals: timeoutMs must be between 1ms and 24h, or "never"')
|
|
237
291
|
return Object.freeze({
|
|
238
292
|
id: event.approvalId,
|
|
239
293
|
sessionId: event.sessionId,
|
|
@@ -242,7 +296,7 @@ export function approvalRequestFromEvent(event: AgentApprovalRequiredEvent): App
|
|
|
242
296
|
capability: event.capability,
|
|
243
297
|
...(event.reason === undefined ? {} : { reason: event.reason }),
|
|
244
298
|
createdAt: event.at,
|
|
245
|
-
expiresAt: event.at
|
|
299
|
+
expiresAt: approvalExpiresAt(event.at, timeoutMs),
|
|
246
300
|
})
|
|
247
301
|
}
|
|
248
302
|
|
package/src/extensions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from "node:fs"
|
|
2
|
-
import { join, relative, resolve } from "node:path"
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
3
3
|
import {
|
|
4
4
|
type WorkflowEvent,
|
|
5
5
|
type WorkflowResult,
|
|
@@ -398,5 +398,9 @@ function isWithin(root: string, candidate: string): boolean {
|
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
400
|
const relativePath = relative(canonical(root), canonical(candidate))
|
|
401
|
-
|
|
401
|
+
// On Windows a path on another drive has no relative form, so `relative` returns it absolute.
|
|
402
|
+
return (
|
|
403
|
+
relativePath === "" ||
|
|
404
|
+
(relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath))
|
|
405
|
+
)
|
|
402
406
|
}
|
package/src/host.ts
CHANGED
|
@@ -191,31 +191,40 @@ export class CodingAgentHost {
|
|
|
191
191
|
const sessionId = this.session.id
|
|
192
192
|
let nextMessage = message
|
|
193
193
|
let repairAttempts = 0
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
194
|
+
// A reader that leaves early (an RPC client disconnecting) never records the backend's
|
|
195
|
+
// `approval.resolved`, so the turn's approvals are denied here instead of waiting on a timer.
|
|
196
|
+
const raised = new Set<string>()
|
|
197
|
+
try {
|
|
198
|
+
for (;;) {
|
|
199
|
+
const stream = this.backend.send({
|
|
200
|
+
sessionId,
|
|
201
|
+
message: nextMessage,
|
|
202
|
+
...(signal === undefined ? {} : { signal }),
|
|
203
|
+
})
|
|
204
|
+
for await (const rawEvent of stream) {
|
|
205
|
+
const event = this.acceptBackendEvent(rawEvent)
|
|
206
|
+
await this.recordEvent(event)
|
|
207
|
+
if (event.type === "approval.required") raised.add(event.approvalId)
|
|
208
|
+
yield event
|
|
209
|
+
if (event.type === "session.stopped") return
|
|
210
|
+
if (event.type === "session.failed") {
|
|
211
|
+
if (event.error.code === "BACKEND_PROTOCOL")
|
|
212
|
+
await this.backend.cancel(sessionId, "backend protocol error").catch(() => {})
|
|
213
|
+
return
|
|
214
|
+
}
|
|
209
215
|
}
|
|
210
|
-
|
|
211
|
-
if (signal?.aborted === true) return
|
|
216
|
+
if (signal?.aborted === true) return
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
const repairTasks = await this.verifyTurn()
|
|
219
|
+
for (const event of repairTasks.events) yield event
|
|
220
|
+
if (repairTasks.tasks.length === 0 || repairAttempts >= this.maxRepairAttempts) return
|
|
216
221
|
|
|
217
|
-
|
|
218
|
-
|
|
222
|
+
repairAttempts++
|
|
223
|
+
nextMessage = createRepairPrompt(repairTasks.tasks, repairAttempts, this.maxRepairAttempts)
|
|
224
|
+
}
|
|
225
|
+
} finally {
|
|
226
|
+
for (const approvalId of raised)
|
|
227
|
+
this.approvals.resolve(approvalId, false, "approval turn ended")
|
|
219
228
|
}
|
|
220
229
|
}
|
|
221
230
|
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { PiBackend, type PiBackendOptions } from "@nifrajs/pi"
|
|
2
2
|
export {
|
|
3
|
+
APPROVAL_NEVER_EXPIRES_AT,
|
|
3
4
|
type ApprovalDecision,
|
|
4
5
|
ApprovalManager,
|
|
5
6
|
type ApprovalManagerOptions,
|
|
6
7
|
type ApprovalMatchRejection,
|
|
7
8
|
type ApprovalMatchResult,
|
|
8
9
|
type ApprovalRequest,
|
|
10
|
+
type ApprovalTimeout,
|
|
9
11
|
approvalRequestFromEvent,
|
|
10
12
|
approvalResolvedEvent,
|
|
11
13
|
} from "./approvals.ts"
|
package/src/isolated.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs"
|
|
2
|
-
import { isAbsolute, relative, resolve } from "node:path"
|
|
2
|
+
import { isAbsolute, relative, resolve, sep } from "node:path"
|
|
3
3
|
import { fileURLToPath } from "node:url"
|
|
4
4
|
import { readBoundedText } from "./process.ts"
|
|
5
5
|
|
|
@@ -321,7 +321,8 @@ export class IsolatedExtensionWorker {
|
|
|
321
321
|
|
|
322
322
|
function isWithin(root: string, candidate: string): boolean {
|
|
323
323
|
const path = relative(resolve(root), resolve(candidate))
|
|
324
|
-
|
|
324
|
+
// On Windows a path on another drive has no relative form, so `relative` returns it absolute.
|
|
325
|
+
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path))
|
|
325
326
|
}
|
|
326
327
|
|
|
327
328
|
function isValidPort(value: unknown): value is number {
|
package/src/native.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type SendMessageInput,
|
|
22
22
|
splitAgentText,
|
|
23
23
|
} from "@nifrajs/agent-protocol"
|
|
24
|
+
import { type ApprovalTimeout, isApprovalTimeout } from "./approvals.ts"
|
|
24
25
|
|
|
25
26
|
export interface NativeMessage {
|
|
26
27
|
readonly role: "user" | "assistant" | "tool"
|
|
@@ -135,8 +136,12 @@ export interface NifraBackendOptions {
|
|
|
135
136
|
readonly model: NativeModelPort
|
|
136
137
|
readonly tools?: readonly NativeTool[]
|
|
137
138
|
readonly approval?: NativeApprovalPort
|
|
138
|
-
/**
|
|
139
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Maximum time a protocol-visible native approval may remain unresolved (default 5 minutes, at
|
|
141
|
+
* most 24 h). `"never"` removes the timer: the approval waits for an answer, a cancel, the end of
|
|
142
|
+
* its turn, or session close.
|
|
143
|
+
*/
|
|
144
|
+
readonly approvalTimeoutMs?: ApprovalTimeout
|
|
140
145
|
readonly maxSteps?: number
|
|
141
146
|
/** Longest message `send` accepts (default 64 KiB). */
|
|
142
147
|
readonly maxMessageChars?: number
|
|
@@ -175,7 +180,7 @@ interface NativePendingApproval {
|
|
|
175
180
|
readonly action: string
|
|
176
181
|
readonly capability: string
|
|
177
182
|
readonly resolve: (resolution: NativeApprovalResolution) => void
|
|
178
|
-
readonly timer: ReturnType<typeof setTimeout>
|
|
183
|
+
readonly timer: ReturnType<typeof setTimeout> | undefined
|
|
179
184
|
readonly onAbort: () => void
|
|
180
185
|
readonly signal: AbortSignal
|
|
181
186
|
}
|
|
@@ -238,11 +243,11 @@ export class NifraBackend implements AgentBackend {
|
|
|
238
243
|
throw new RangeError("nifra backend: maxHistoryChars must be at least 256")
|
|
239
244
|
if (
|
|
240
245
|
this.options.approvalTimeoutMs !== undefined &&
|
|
241
|
-
|
|
242
|
-
this.options.approvalTimeoutMs < 1 ||
|
|
243
|
-
this.options.approvalTimeoutMs > 24 * 60 * 60_000)
|
|
246
|
+
!isApprovalTimeout(this.options.approvalTimeoutMs)
|
|
244
247
|
)
|
|
245
|
-
throw new RangeError(
|
|
248
|
+
throw new RangeError(
|
|
249
|
+
'nifra backend: approvalTimeoutMs must be between 1ms and 24h, or "never"',
|
|
250
|
+
)
|
|
246
251
|
}
|
|
247
252
|
|
|
248
253
|
async createSession(input: CreateSessionInput): Promise<AgentSessionSnapshot> {
|
|
@@ -627,13 +632,14 @@ export class NifraBackend implements AgentBackend {
|
|
|
627
632
|
session.closed ? "APPROVAL_CLOSED" : "APPROVAL_CANCELLED",
|
|
628
633
|
)
|
|
629
634
|
}
|
|
630
|
-
const
|
|
631
|
-
|
|
635
|
+
const timeoutMs = this.options.approvalTimeoutMs ?? 5 * 60_000
|
|
636
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
637
|
+
if (timeoutMs !== "never") {
|
|
638
|
+
timer = setTimeout(() => {
|
|
632
639
|
this.settleApproval(session, approvalId, false, "approval timed out", "APPROVAL_TIMEOUT")
|
|
633
|
-
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
;(timer as unknown as { unref?: () => void }).unref?.()
|
|
640
|
+
}, timeoutMs)
|
|
641
|
+
;(timer as unknown as { unref?: () => void }).unref?.()
|
|
642
|
+
}
|
|
637
643
|
pending = {
|
|
638
644
|
id: approvalId,
|
|
639
645
|
sessionId: session.id,
|
package/src/process.ts
CHANGED
|
@@ -70,13 +70,15 @@ export interface BoundedProcessResult {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const KILL_GRACE_MS = 2_000
|
|
73
|
+
// How long the pipes are still read once the child has exited, for output it wrote just before.
|
|
74
|
+
const DRAIN_GRACE_MS = 2_000
|
|
73
75
|
const PROCESS_GROUPS = process.platform !== "win32"
|
|
74
76
|
|
|
75
77
|
/**
|
|
76
78
|
* Run a trusted command to completion within `timeoutMs`. On POSIX the child leads its own process
|
|
77
79
|
* group, so a timeout reaches every process it started: SIGTERM, then SIGKILL after a grace period.
|
|
78
|
-
*
|
|
79
|
-
* waited on,
|
|
80
|
+
* The call settles when the child exits: a process it left running that still holds the pipes open
|
|
81
|
+
* is read from for a short grace and then no longer waited on, and `timedOut` stays `false`.
|
|
80
82
|
*/
|
|
81
83
|
export async function runBoundedProcess(
|
|
82
84
|
argv: readonly string[],
|
|
@@ -110,15 +112,25 @@ export async function runBoundedProcess(
|
|
|
110
112
|
void proc.exited.then(() => reading.abort())
|
|
111
113
|
}, graceMs)
|
|
112
114
|
}, options.timeoutMs)
|
|
115
|
+
let drain: ReturnType<typeof setTimeout> | undefined
|
|
116
|
+
// Awaited below, so the drain timer is armed before the `finally` that clears it can run.
|
|
117
|
+
const exited = proc.exited.then((status) => {
|
|
118
|
+
if (!timedOut) {
|
|
119
|
+
clearTimeout(timer)
|
|
120
|
+
drain = setTimeout(() => reading.abort(), DRAIN_GRACE_MS)
|
|
121
|
+
}
|
|
122
|
+
return status
|
|
123
|
+
})
|
|
113
124
|
try {
|
|
114
125
|
const [stdout, stderr, status] = await Promise.all([
|
|
115
126
|
readBoundedText(proc.stdout, options.maxStdoutBytes, reading.signal),
|
|
116
127
|
readBoundedText(proc.stderr, options.maxStderrBytes, reading.signal),
|
|
117
|
-
|
|
128
|
+
exited,
|
|
118
129
|
])
|
|
119
130
|
return { stdout, stderr, status, timedOut }
|
|
120
131
|
} finally {
|
|
121
132
|
clearTimeout(timer)
|
|
122
133
|
clearTimeout(escalation)
|
|
134
|
+
clearTimeout(drain)
|
|
123
135
|
}
|
|
124
136
|
}
|
package/src/rpc.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface CodingAgentRpcServerOptions {
|
|
|
37
37
|
readonly maxRepairAttempts?: number
|
|
38
38
|
readonly verification?: CodingAgentHostOptions["verification"]
|
|
39
39
|
readonly extensionEvents?: CodingAgentHostOptions["extensionEvents"]
|
|
40
|
+
/**
|
|
41
|
+
* The host's approval broker: how long an approval or a tool's question waits for its answer
|
|
42
|
+
* (`timeoutMs`, default 5 minutes, or `"never"`), how many may wait (`maxPending`, default 32),
|
|
43
|
+
* and its `onRequired`/`onResolved` hooks.
|
|
44
|
+
*/
|
|
45
|
+
readonly approvals?: CodingAgentHostOptions["approvals"]
|
|
40
46
|
/**
|
|
41
47
|
* Keep a `turn.send` running when its client disconnects, and let a client that comes back read
|
|
42
48
|
* what it missed with `turn.attach` (an `afterSeq` cursor, or `Last-Event-ID`). The newest
|
|
@@ -124,6 +130,7 @@ export class CodingAgentRpcServer {
|
|
|
124
130
|
...(options.extensionEvents === undefined
|
|
125
131
|
? {}
|
|
126
132
|
: { extensionEvents: options.extensionEvents }),
|
|
133
|
+
...(options.approvals === undefined ? {} : { approvals: options.approvals }),
|
|
127
134
|
})
|
|
128
135
|
this.resumable = resumableTurnConfig(options.resumableTurns)
|
|
129
136
|
this.token = options.authToken ?? randomAuthToken()
|