pi-subagents 0.35.1 → 0.37.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 +71 -0
- package/README.md +224 -31
- package/agents/advisor.md +73 -0
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +11 -13
- package/skills/pi-subagents/SKILL.md +42 -13
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +121 -17
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +127 -0
- package/src/api/preflight.ts +399 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +52 -38
- package/src/extension/rpc.ts +31 -2
- package/src/extension/schemas.ts +29 -3
- package/src/extension/tool-description.ts +4 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +158 -17
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +47 -8
- package/src/runs/background/async-status.ts +94 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +10 -2
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +406 -54
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +181 -111
- package/src/runs/foreground/execution.ts +147 -47
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +426 -165
- package/src/runs/shared/acceptance.ts +94 -41
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +5 -5
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +35 -3
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +11 -0
- package/src/runs/shared/pi-args.ts +148 -56
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +31 -19
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +21 -3
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +314 -28
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +153 -6
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +15 -6
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +17 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
3
|
+
SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
|
|
3
4
|
type SubagentDelegationAcceptanceResult,
|
|
5
|
+
type SubagentDelegationEffectsResult,
|
|
6
|
+
type SubagentDelegationExecutionResult,
|
|
4
7
|
type SubagentDelegationRequest,
|
|
5
8
|
type SubagentDelegationResponse,
|
|
9
|
+
type SubagentDelegationReviewResult,
|
|
6
10
|
type SubagentDelegationStatus,
|
|
7
11
|
type SubagentDelegationUpdate,
|
|
12
|
+
type SubagentDelegationV2Request,
|
|
13
|
+
type SubagentDelegationV2Response,
|
|
14
|
+
type SubagentDelegationV2Thinking,
|
|
15
|
+
type SubagentDelegationV2Value,
|
|
16
|
+
type SubagentDelegationV2Update,
|
|
8
17
|
} from "../api/delegation.ts";
|
|
9
|
-
import type { AcceptanceInput, ToolBudgetConfig, TurnBudgetConfig } from "../shared/types.ts";
|
|
18
|
+
import type { AcceptanceInput, AgentContract, EffectsProjection, ExecutionProjection, JsonSchemaObject, ReviewProjection, ToolBudgetConfig, TurnBudgetConfig, Usage } from "../shared/types.ts";
|
|
19
|
+
import { isAgentContractV1 } from "../runs/shared/agent-contract.ts";
|
|
20
|
+
import { cloneJsonWithinByteLimit } from "./delegation-json.ts";
|
|
10
21
|
|
|
11
22
|
export interface PromptTemplateDelegationTask {
|
|
12
23
|
agent: string;
|
|
@@ -86,15 +97,22 @@ export interface PromptTemplateBridgeResult {
|
|
|
86
97
|
exitCode?: number;
|
|
87
98
|
error?: string;
|
|
88
99
|
model?: string;
|
|
100
|
+
thinking?: string;
|
|
101
|
+
structuredOutput?: unknown;
|
|
89
102
|
interrupted?: boolean;
|
|
90
103
|
timedOut?: boolean;
|
|
91
104
|
stopped?: boolean;
|
|
92
105
|
turnBudgetExceeded?: boolean;
|
|
93
106
|
toolBudgetBlocked?: boolean;
|
|
107
|
+
structuredOutputFailed?: boolean;
|
|
94
108
|
savedOutputPath?: string;
|
|
95
109
|
sessionFile?: string;
|
|
110
|
+
agentContract?: AgentContract;
|
|
111
|
+
execution?: ExecutionProjection;
|
|
96
112
|
acceptance?: SubagentDelegationAcceptanceResult;
|
|
97
|
-
|
|
113
|
+
review?: ReviewProjection;
|
|
114
|
+
effects?: EffectsProjection;
|
|
115
|
+
usage?: Usage;
|
|
98
116
|
progressSummary?: { toolCount?: number; durationMs?: number; tokens?: number };
|
|
99
117
|
skillsWarning?: string;
|
|
100
118
|
outputSaveError?: string;
|
|
@@ -125,12 +143,20 @@ export interface DelegatedSubagentExecutionParams {
|
|
|
125
143
|
worktree?: boolean;
|
|
126
144
|
timeoutMs?: number;
|
|
127
145
|
turnBudget?: TurnBudgetConfig;
|
|
146
|
+
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
147
|
+
enforceHardTurnLimit?: boolean;
|
|
128
148
|
toolBudget?: ToolBudgetConfig;
|
|
129
149
|
skill?: string | string[] | boolean;
|
|
130
150
|
output?: string | boolean;
|
|
131
151
|
outputMode?: "inline" | "file-only";
|
|
152
|
+
outputSchema?: JsonSchemaObject;
|
|
153
|
+
agentContract?: AgentContract;
|
|
132
154
|
acceptance?: AcceptanceInput;
|
|
133
155
|
artifacts?: boolean;
|
|
156
|
+
/** Internal-only thinking override accepted by executeDelegated. */
|
|
157
|
+
delegatedThinkingOverride?: SubagentDelegationV2Thinking;
|
|
158
|
+
/** Internal-only V2 capability accepted and stripped by executeDelegated. */
|
|
159
|
+
delegatedAllowZeroToolBudget?: true;
|
|
134
160
|
async: false;
|
|
135
161
|
foregroundOnly: true;
|
|
136
162
|
clarify: false;
|
|
@@ -175,8 +201,8 @@ export function parsePromptTemplateRequest(data: unknown): PromptTemplateDelegat
|
|
|
175
201
|
const fallbackTask = tasks[0];
|
|
176
202
|
return {
|
|
177
203
|
requestId: value.requestId,
|
|
178
|
-
agent: hasSingle ? value.agent : fallbackTask!.agent,
|
|
179
|
-
task: hasSingle ? value.task : fallbackTask!.task,
|
|
204
|
+
agent: hasSingle ? value.agent! : fallbackTask!.agent,
|
|
205
|
+
task: hasSingle ? value.task! : fallbackTask!.task,
|
|
180
206
|
...(tasks.length > 0 ? { tasks } : {}),
|
|
181
207
|
context: value.context,
|
|
182
208
|
model: value.model,
|
|
@@ -343,10 +369,13 @@ export function toSubagentDelegationExecutionParams(request: SubagentDelegationR
|
|
|
343
369
|
model: request.model,
|
|
344
370
|
timeoutMs: request.timeoutMs,
|
|
345
371
|
turnBudget: request.turnBudget,
|
|
372
|
+
enforceHardTurnLimit: true,
|
|
346
373
|
toolBudget: request.toolBudget,
|
|
347
374
|
skill: request.skill,
|
|
348
375
|
output: request.output,
|
|
349
376
|
outputMode: request.outputMode,
|
|
377
|
+
outputSchema: request.outputSchema,
|
|
378
|
+
agentContract: request.agentContract,
|
|
350
379
|
acceptance: request.acceptance,
|
|
351
380
|
artifacts: request.artifacts,
|
|
352
381
|
async: false,
|
|
@@ -355,6 +384,30 @@ export function toSubagentDelegationExecutionParams(request: SubagentDelegationR
|
|
|
355
384
|
};
|
|
356
385
|
}
|
|
357
386
|
|
|
387
|
+
export function toSubagentDelegationV2ExecutionParams(request: SubagentDelegationV2Request): DelegatedSubagentExecutionParams {
|
|
388
|
+
return {
|
|
389
|
+
agent: request.agent,
|
|
390
|
+
task: request.task,
|
|
391
|
+
context: request.context,
|
|
392
|
+
cwd: request.cwd,
|
|
393
|
+
model: request.model,
|
|
394
|
+
timeoutMs: request.timeoutMs,
|
|
395
|
+
turnBudget: request.turnBudget,
|
|
396
|
+
enforceHardTurnLimit: true,
|
|
397
|
+
toolBudget: request.toolBudget,
|
|
398
|
+
skill: request.skill,
|
|
399
|
+
output: false,
|
|
400
|
+
...(request.result.kind === "structured" ? { outputSchema: request.result.schema } : {}),
|
|
401
|
+
acceptance: false,
|
|
402
|
+
artifacts: request.artifacts,
|
|
403
|
+
delegatedThinkingOverride: request.thinking,
|
|
404
|
+
delegatedAllowZeroToolBudget: true,
|
|
405
|
+
async: false,
|
|
406
|
+
foregroundOnly: true,
|
|
407
|
+
clarify: false,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
358
411
|
export function toSubagentDelegationUpdate(requestId: string, result: PromptTemplateBridgeResult): SubagentDelegationUpdate | undefined {
|
|
359
412
|
const legacy = toDelegationUpdate(requestId, result);
|
|
360
413
|
if (!legacy) return undefined;
|
|
@@ -373,6 +426,29 @@ export function toSubagentDelegationUpdate(requestId: string, result: PromptTemp
|
|
|
373
426
|
};
|
|
374
427
|
}
|
|
375
428
|
|
|
429
|
+
export function toSubagentDelegationV2Update(
|
|
430
|
+
request: SubagentDelegationV2Request,
|
|
431
|
+
result: PromptTemplateBridgeResult,
|
|
432
|
+
): SubagentDelegationV2Update | undefined {
|
|
433
|
+
const legacy = toDelegationUpdate(request.requestId, result);
|
|
434
|
+
if (!legacy) return undefined;
|
|
435
|
+
return {
|
|
436
|
+
version: SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
|
|
437
|
+
requestId: request.requestId,
|
|
438
|
+
ownerRunId: request.ownerRunId,
|
|
439
|
+
nodeId: request.nodeId,
|
|
440
|
+
...(legacy.currentTool ? { currentTool: legacy.currentTool } : {}),
|
|
441
|
+
...(legacy.currentToolArgs ? { currentToolArgs: legacy.currentToolArgs } : {}),
|
|
442
|
+
...(legacy.recentOutput ? { recentOutput: legacy.recentOutput } : {}),
|
|
443
|
+
...(legacy.recentOutputLines ? { recentOutputLines: legacy.recentOutputLines } : {}),
|
|
444
|
+
...(legacy.recentTools ? { recentTools: legacy.recentTools } : {}),
|
|
445
|
+
...(legacy.model ? { model: legacy.model } : {}),
|
|
446
|
+
...(typeof legacy.toolCount === "number" ? { toolCount: legacy.toolCount } : {}),
|
|
447
|
+
...(typeof legacy.durationMs === "number" ? { durationMs: legacy.durationMs } : {}),
|
|
448
|
+
...(typeof legacy.tokens === "number" ? { tokens: legacy.tokens } : {}),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
376
452
|
function resolveSubagentDelegationStatus(
|
|
377
453
|
result: PromptTemplateBridgeResult,
|
|
378
454
|
aborted: boolean,
|
|
@@ -381,9 +457,10 @@ function resolveSubagentDelegationStatus(
|
|
|
381
457
|
const child = result.details?.results?.[0];
|
|
382
458
|
if (!child) return "failed";
|
|
383
459
|
if (result.details?.timedOut || child.timedOut) return "timed_out";
|
|
460
|
+
if (child?.structuredOutputFailed) return "structured_output_failed";
|
|
384
461
|
if (child?.turnBudgetExceeded) return "turn_budget_exhausted";
|
|
385
462
|
if (child?.toolBudgetBlocked) return "tool_budget_exhausted";
|
|
386
|
-
if (child?.acceptance?.status === "rejected" && child.acceptance.explicit) return "acceptance_failed";
|
|
463
|
+
if (!isAgentContractV1(child.agentContract) && child?.acceptance?.status === "rejected" && child.acceptance.explicit) return "acceptance_failed";
|
|
387
464
|
if (result.details?.stopped || child?.stopped || child?.interrupted) return "interrupted";
|
|
388
465
|
if (result.isError || child?.error || (typeof child?.exitCode === "number" && child.exitCode !== 0)) return "failed";
|
|
389
466
|
return "completed";
|
|
@@ -410,10 +487,13 @@ export function toSubagentDelegationResponse(
|
|
|
410
487
|
...(child?.agent ? { agent: child.agent } : {}),
|
|
411
488
|
...(child?.model ? { model: child.model } : {}),
|
|
412
489
|
...(typeof child?.exitCode === "number" ? { exitCode: child.exitCode } : {}),
|
|
490
|
+
...(child?.execution ? { execution: child.execution as SubagentDelegationExecutionResult } : {}),
|
|
413
491
|
...(child?.finalOutput ? { output: child.finalOutput } : {}),
|
|
414
492
|
...(child?.savedOutputPath ? { outputPath: child.savedOutputPath } : {}),
|
|
415
493
|
...(child?.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
416
|
-
...(child?.acceptance ? { acceptance: { status: child.acceptance.status, explicit: child.acceptance.explicit } } : {}),
|
|
494
|
+
...(child?.acceptance ? { acceptance: { status: child.acceptance.status, evidenceStatus: child.acceptance.evidenceStatus, explicit: child.acceptance.explicit } } : {}),
|
|
495
|
+
...(child?.review ? { review: child.review as SubagentDelegationReviewResult } : {}),
|
|
496
|
+
...(child?.effects ? { effects: child.effects as SubagentDelegationEffectsResult } : {}),
|
|
417
497
|
...(typeof child?.usage?.turns === "number" ? { turns: child.usage.turns } : {}),
|
|
418
498
|
...(typeof progress?.toolCount === "number" ? { toolCount: progress.toolCount } : {}),
|
|
419
499
|
...(typeof progress?.durationMs === "number" ? { durationMs: progress.durationMs } : {}),
|
|
@@ -422,6 +502,73 @@ export function toSubagentDelegationResponse(
|
|
|
422
502
|
};
|
|
423
503
|
}
|
|
424
504
|
|
|
505
|
+
const MAX_V2_RESULT_BYTES = 1024 * 1024;
|
|
506
|
+
|
|
507
|
+
export function toSubagentDelegationV2Response(
|
|
508
|
+
request: SubagentDelegationV2Request,
|
|
509
|
+
result: PromptTemplateBridgeResult,
|
|
510
|
+
aborted: boolean,
|
|
511
|
+
): SubagentDelegationV2Response {
|
|
512
|
+
const child = result.details?.results?.[0];
|
|
513
|
+
const progress = child?.progressSummary ?? result.details?.progress?.[0];
|
|
514
|
+
let status = resolveSubagentDelegationStatus(result, aborted);
|
|
515
|
+
let error = child?.error ?? (status === "failed" ? firstTextContent(result.content) : undefined);
|
|
516
|
+
let projectedResult: SubagentDelegationV2Value | undefined;
|
|
517
|
+
if (status === "completed") {
|
|
518
|
+
if (request.result.kind === "text") {
|
|
519
|
+
if (typeof child?.finalOutput !== "string") {
|
|
520
|
+
status = "failed";
|
|
521
|
+
error = "Delegated subagent did not capture a text result.";
|
|
522
|
+
} else if (Buffer.byteLength(child.finalOutput, "utf8") > MAX_V2_RESULT_BYTES) {
|
|
523
|
+
status = "failed";
|
|
524
|
+
error = "Delegated text result exceeds 1 MiB when UTF-8 encoded.";
|
|
525
|
+
} else {
|
|
526
|
+
projectedResult = { kind: "text", text: child.finalOutput };
|
|
527
|
+
}
|
|
528
|
+
} else if (child?.structuredOutput === undefined) {
|
|
529
|
+
status = "failed";
|
|
530
|
+
error = "Delegated subagent did not capture the requested structured result.";
|
|
531
|
+
} else {
|
|
532
|
+
const inspected = cloneJsonWithinByteLimit(child.structuredOutput, MAX_V2_RESULT_BYTES);
|
|
533
|
+
if (!inspected.ok) {
|
|
534
|
+
status = "failed";
|
|
535
|
+
error = inspected.reason === "too_large"
|
|
536
|
+
? "Delegated structured result exceeds 1 MiB when encoded."
|
|
537
|
+
: "Delegated structured result is not plain JSON data.";
|
|
538
|
+
} else {
|
|
539
|
+
projectedResult = { kind: "structured", value: inspected.value };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const usage = child?.usage;
|
|
544
|
+
return {
|
|
545
|
+
version: SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
|
|
546
|
+
requestId: request.requestId,
|
|
547
|
+
ownerRunId: request.ownerRunId,
|
|
548
|
+
nodeId: request.nodeId,
|
|
549
|
+
status,
|
|
550
|
+
...(error ? { error } : {}),
|
|
551
|
+
...(result.details?.runId ? { runId: result.details.runId } : {}),
|
|
552
|
+
...(child?.agent ? { agent: child.agent } : {}),
|
|
553
|
+
...(child?.model ? { model: child.model } : {}),
|
|
554
|
+
...(child?.thinking ? { thinking: child.thinking } : {}),
|
|
555
|
+
...(typeof child?.exitCode === "number" ? { exitCode: child.exitCode } : {}),
|
|
556
|
+
...(projectedResult ? { result: projectedResult } : {}),
|
|
557
|
+
...(usage ? {
|
|
558
|
+
usage: {
|
|
559
|
+
input: usage.input,
|
|
560
|
+
output: usage.output,
|
|
561
|
+
cacheRead: usage.cacheRead,
|
|
562
|
+
cacheWrite: usage.cacheWrite,
|
|
563
|
+
cost: usage.cost,
|
|
564
|
+
turns: usage.turns,
|
|
565
|
+
toolCalls: progress?.toolCount ?? 0,
|
|
566
|
+
durationMs: progress?.durationMs ?? 0,
|
|
567
|
+
},
|
|
568
|
+
} : {}),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
425
572
|
export function toPromptTemplateResponse(
|
|
426
573
|
request: PromptTemplateDelegationRequest,
|
|
427
574
|
result: PromptTemplateBridgeResult,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const MAX_JSON_DEPTH = 64;
|
|
2
|
+
const MAX_JSON_ENTRIES = 100_000;
|
|
3
|
+
|
|
4
|
+
export type BoundedJsonClone =
|
|
5
|
+
| { ok: true; value: unknown; encodedBytes: number }
|
|
6
|
+
| { ok: false; reason: "invalid" | "too_large" };
|
|
7
|
+
|
|
8
|
+
/** Clone plain JSON data without invoking getters or toJSON hooks. */
|
|
9
|
+
export function cloneJsonWithinByteLimit(input: unknown, maxBytes: number): BoundedJsonClone {
|
|
10
|
+
let minimumBytes = 0;
|
|
11
|
+
let entries = 0;
|
|
12
|
+
const active = new WeakSet<object>();
|
|
13
|
+
|
|
14
|
+
const addMinimumBytes = (bytes: number): void => {
|
|
15
|
+
minimumBytes += bytes;
|
|
16
|
+
if (minimumBytes > maxBytes) throw new RangeError("too_large");
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const visit = (value: unknown, depth: number): unknown => {
|
|
20
|
+
if (depth > MAX_JSON_DEPTH) throw new TypeError("invalid");
|
|
21
|
+
if (value === null) {
|
|
22
|
+
addMinimumBytes(4);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === "boolean") {
|
|
26
|
+
addMinimumBytes(value ? 4 : 5);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "number") {
|
|
30
|
+
if (!Number.isFinite(value)) throw new TypeError("invalid");
|
|
31
|
+
const encoded = JSON.stringify(Object.is(value, -0) ? 0 : value);
|
|
32
|
+
addMinimumBytes(Buffer.byteLength(encoded, "utf8"));
|
|
33
|
+
return Object.is(value, -0) ? 0 : value;
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === "string") {
|
|
36
|
+
// Quoting and escaping can only increase the encoded size.
|
|
37
|
+
addMinimumBytes(Buffer.byteLength(value, "utf8") + 2);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value !== "object") throw new TypeError("invalid");
|
|
41
|
+
|
|
42
|
+
const object = value as object;
|
|
43
|
+
if (active.has(object)) throw new TypeError("invalid");
|
|
44
|
+
active.add(object);
|
|
45
|
+
try {
|
|
46
|
+
const prototype = Object.getPrototypeOf(object);
|
|
47
|
+
const keys = Reflect.ownKeys(object);
|
|
48
|
+
if (Array.isArray(object)) {
|
|
49
|
+
if (prototype !== Array.prototype) throw new TypeError("invalid");
|
|
50
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(object, "length");
|
|
51
|
+
if (!lengthDescriptor || !("value" in lengthDescriptor) || !Number.isSafeInteger(lengthDescriptor.value) || lengthDescriptor.value < 0) {
|
|
52
|
+
throw new TypeError("invalid");
|
|
53
|
+
}
|
|
54
|
+
const length = lengthDescriptor.value as number;
|
|
55
|
+
addMinimumBytes(2 + Math.max(0, length - 1));
|
|
56
|
+
const output: unknown[] = [];
|
|
57
|
+
for (const key of keys) {
|
|
58
|
+
if (typeof key === "symbol") throw new TypeError("invalid");
|
|
59
|
+
if (key === "length") continue;
|
|
60
|
+
if (!/^(?:0|[1-9][0-9]*)$/.test(key) || Number(key) >= length) throw new TypeError("invalid");
|
|
61
|
+
}
|
|
62
|
+
for (let index = 0; index < length; index++) {
|
|
63
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, String(index));
|
|
64
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) throw new TypeError("invalid");
|
|
65
|
+
entries++;
|
|
66
|
+
if (entries > MAX_JSON_ENTRIES) throw new TypeError("invalid");
|
|
67
|
+
output.push(visit(descriptor.value, depth + 1));
|
|
68
|
+
}
|
|
69
|
+
return output;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (prototype !== Object.prototype && prototype !== null) throw new TypeError("invalid");
|
|
73
|
+
addMinimumBytes(2 + Math.max(0, keys.length - 1));
|
|
74
|
+
const output: Record<string, unknown> = {};
|
|
75
|
+
for (const key of keys) {
|
|
76
|
+
if (typeof key === "symbol") throw new TypeError("invalid");
|
|
77
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
78
|
+
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) throw new TypeError("invalid");
|
|
79
|
+
entries++;
|
|
80
|
+
if (entries > MAX_JSON_ENTRIES) throw new TypeError("invalid");
|
|
81
|
+
addMinimumBytes(Buffer.byteLength(JSON.stringify(key), "utf8") + 1);
|
|
82
|
+
Object.defineProperty(output, key, {
|
|
83
|
+
value: visit(descriptor.value, depth + 1),
|
|
84
|
+
enumerable: true,
|
|
85
|
+
configurable: true,
|
|
86
|
+
writable: true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return output;
|
|
90
|
+
} finally {
|
|
91
|
+
active.delete(object);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const value = visit(input, 0);
|
|
97
|
+
const encoded = JSON.stringify(value);
|
|
98
|
+
if (encoded === undefined) return { ok: false, reason: "invalid" };
|
|
99
|
+
const encodedBytes = Buffer.byteLength(encoded, "utf8");
|
|
100
|
+
if (encodedBytes > maxBytes) return { ok: false, reason: "too_large" };
|
|
101
|
+
return { ok: true, value, encodedBytes };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
reason: error instanceof RangeError && error.message === "too_large" ? "too_large" : "invalid",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
3
|
+
SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION,
|
|
3
4
|
type SubagentDelegationRequest,
|
|
5
|
+
type SubagentDelegationV2Request,
|
|
4
6
|
} from "../api/delegation.ts";
|
|
5
7
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
6
8
|
import { validateToolBudgetConfig } from "../runs/shared/tool-budget.ts";
|
|
7
9
|
import { resolveTurnBudgetConfig } from "../runs/shared/turn-budget.ts";
|
|
10
|
+
import { cloneJsonWithinByteLimit } from "./delegation-json.ts";
|
|
8
11
|
|
|
9
12
|
export type SubagentDelegationParseResult =
|
|
10
|
-
| { ok: true; request: SubagentDelegationRequest }
|
|
11
|
-
| { ok: false; requestId?: string; error: string };
|
|
13
|
+
| { ok: true; request: SubagentDelegationRequest | SubagentDelegationV2Request }
|
|
14
|
+
| { ok: false; version?: 1 | 2; requestId?: string; ownerRunId?: string; nodeId?: string; error: string };
|
|
12
15
|
|
|
13
|
-
const
|
|
16
|
+
const v1SupportedFields = new Set([
|
|
14
17
|
"version",
|
|
15
18
|
"requestId",
|
|
16
19
|
"agent",
|
|
@@ -24,67 +27,178 @@ const supportedFields = new Set([
|
|
|
24
27
|
"skill",
|
|
25
28
|
"output",
|
|
26
29
|
"outputMode",
|
|
30
|
+
"outputSchema",
|
|
31
|
+
"agentContract",
|
|
27
32
|
"acceptance",
|
|
28
33
|
"artifacts",
|
|
29
34
|
]);
|
|
30
35
|
|
|
36
|
+
const v2SupportedFields = new Set([
|
|
37
|
+
"version",
|
|
38
|
+
"requestId",
|
|
39
|
+
"ownerRunId",
|
|
40
|
+
"nodeId",
|
|
41
|
+
"agent",
|
|
42
|
+
"task",
|
|
43
|
+
"context",
|
|
44
|
+
"cwd",
|
|
45
|
+
"model",
|
|
46
|
+
"thinking",
|
|
47
|
+
"timeoutMs",
|
|
48
|
+
"turnBudget",
|
|
49
|
+
"toolBudget",
|
|
50
|
+
"skill",
|
|
51
|
+
"artifacts",
|
|
52
|
+
"result",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const v2ThinkingLevels = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
56
|
+
const MAX_SCHEMA_BYTES = 64 * 1024;
|
|
57
|
+
const MAX_V2_TASK_BYTES = 1024 * 1024;
|
|
58
|
+
const MAX_V2_CWD_BYTES = 32 * 1024;
|
|
59
|
+
const MAX_V2_SHORT_TEXT_BYTES = 1024;
|
|
60
|
+
const MAX_V2_SKILL_ENTRIES = 256;
|
|
61
|
+
const MAX_V2_SKILL_AGGREGATE_BYTES = 64 * 1024;
|
|
62
|
+
|
|
31
63
|
function nonEmptyString(value: unknown): value is string {
|
|
32
64
|
return typeof value === "string" && value.trim().length > 0;
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
function
|
|
67
|
+
function validateId(value: unknown): string | undefined {
|
|
36
68
|
if (!nonEmptyString(value) || value.length > 256 || /[\r\n]/.test(value)) return undefined;
|
|
37
69
|
return value;
|
|
38
70
|
}
|
|
39
71
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (value.version !== SUBAGENT_DELEGATION_PROTOCOL_VERSION) {
|
|
47
|
-
return {
|
|
48
|
-
...(requestId ? { requestId } : {}),
|
|
49
|
-
ok: false,
|
|
50
|
-
error: `Unsupported delegation protocol version: ${String(value.version)}.`,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (!requestId) {
|
|
54
|
-
return { ok: false, error: "Delegation requestId must be a non-empty string of at most 256 characters without newlines." };
|
|
55
|
-
}
|
|
56
|
-
const unsupportedField = Object.keys(value).find((key) => !supportedFields.has(key));
|
|
57
|
-
if (unsupportedField) return { ok: false, requestId, error: `Unsupported delegation field: ${unsupportedField}.` };
|
|
58
|
-
if (!nonEmptyString(value.agent)) return { ok: false, requestId, error: "Delegation agent must be a non-empty string." };
|
|
59
|
-
if (!nonEmptyString(value.task)) return { ok: false, requestId, error: "Delegation task must be a non-empty string." };
|
|
72
|
+
function validateSharedFields(
|
|
73
|
+
value: Record<string, unknown>,
|
|
74
|
+
identity: { requestId: string; version?: 1 | 2; ownerRunId?: string; nodeId?: string },
|
|
75
|
+
): SubagentDelegationParseResult | undefined {
|
|
76
|
+
if (!nonEmptyString(value.agent)) return { ok: false, ...identity, error: "Delegation agent must be a non-empty string." };
|
|
77
|
+
if (!nonEmptyString(value.task)) return { ok: false, ...identity, error: "Delegation task must be a non-empty string." };
|
|
60
78
|
if (value.context !== "fresh" && value.context !== "fork") {
|
|
61
|
-
return { ok: false,
|
|
79
|
+
return { ok: false, ...identity, error: "Delegation context must be fresh or fork." };
|
|
62
80
|
}
|
|
63
|
-
if (!nonEmptyString(value.cwd)) return { ok: false,
|
|
81
|
+
if (!nonEmptyString(value.cwd)) return { ok: false, ...identity, error: "Delegation cwd must be a non-empty string." };
|
|
64
82
|
if (value.model !== undefined && !nonEmptyString(value.model)) {
|
|
65
|
-
return { ok: false,
|
|
83
|
+
return { ok: false, ...identity, error: "model must be a non-empty string when provided." };
|
|
66
84
|
}
|
|
67
85
|
if (value.timeoutMs !== undefined && (typeof value.timeoutMs !== "number" || !Number.isInteger(value.timeoutMs) || value.timeoutMs < 1)) {
|
|
68
|
-
return { ok: false,
|
|
86
|
+
return { ok: false, ...identity, error: "timeoutMs must be an integer >= 1." };
|
|
87
|
+
}
|
|
88
|
+
if (identity.version === SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION && value.timeoutMs !== undefined && value.timeoutMs > 2_147_483_647) {
|
|
89
|
+
return { ok: false, ...identity, error: "timeoutMs must be <= 2147483647 for delegation v2." };
|
|
69
90
|
}
|
|
70
91
|
const turnBudget = resolveTurnBudgetConfig(value.turnBudget);
|
|
71
|
-
if (turnBudget.error) return { ok: false,
|
|
92
|
+
if (turnBudget.error) return { ok: false, ...identity, error: turnBudget.error };
|
|
72
93
|
if (value.toolBudget && typeof value.toolBudget === "object" && !Array.isArray(value.toolBudget)) {
|
|
73
94
|
const unsupportedToolBudgetField = Object.keys(value.toolBudget).find((key) => key !== "soft" && key !== "hard" && key !== "block");
|
|
74
95
|
if (unsupportedToolBudgetField) {
|
|
75
|
-
return { ok: false,
|
|
96
|
+
return { ok: false, ...identity, error: `toolBudget.${unsupportedToolBudgetField} is not supported.` };
|
|
76
97
|
}
|
|
77
98
|
}
|
|
78
|
-
const toolBudget = validateToolBudgetConfig(
|
|
79
|
-
|
|
99
|
+
const toolBudget = validateToolBudgetConfig(
|
|
100
|
+
value.toolBudget,
|
|
101
|
+
"toolBudget",
|
|
102
|
+
identity.version === SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION ? { minimumHard: 0 } : undefined,
|
|
103
|
+
);
|
|
104
|
+
if (toolBudget.error) return { ok: false, ...identity, error: toolBudget.error };
|
|
80
105
|
if (value.skill !== undefined) {
|
|
81
106
|
const validSkill = typeof value.skill === "boolean"
|
|
82
107
|
|| nonEmptyString(value.skill)
|
|
83
108
|
|| (Array.isArray(value.skill) && value.skill.length > 0 && value.skill.every(nonEmptyString));
|
|
84
109
|
if (!validSkill) {
|
|
85
|
-
return { ok: false,
|
|
110
|
+
return { ok: false, ...identity, error: "skill must be a boolean, non-empty string, or non-empty string array." };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (value.artifacts !== undefined && typeof value.artifacts !== "boolean") {
|
|
114
|
+
return { ok: false, ...identity, error: "artifacts must be a boolean." };
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseV2(value: Record<string, unknown>, requestId: string): SubagentDelegationParseResult {
|
|
120
|
+
const ownerRunId = validateId(value.ownerRunId);
|
|
121
|
+
if (!ownerRunId) {
|
|
122
|
+
return { ok: false, version: 2, requestId, error: "Delegation ownerRunId must be a non-empty string of at most 256 characters without newlines." };
|
|
123
|
+
}
|
|
124
|
+
const nodeId = validateId(value.nodeId);
|
|
125
|
+
if (!nodeId) {
|
|
126
|
+
return { ok: false, version: 2, requestId, ownerRunId, error: "Delegation nodeId must be a non-empty string of at most 256 characters without newlines." };
|
|
127
|
+
}
|
|
128
|
+
const identity = { version: 2 as const, requestId, ownerRunId, nodeId };
|
|
129
|
+
const unsupportedField = Object.keys(value).find((key) => !v2SupportedFields.has(key));
|
|
130
|
+
if (unsupportedField) return { ok: false, ...identity, error: `Unsupported delegation field: ${unsupportedField}.` };
|
|
131
|
+
const sharedError = validateSharedFields(value, identity);
|
|
132
|
+
if (sharedError) return sharedError;
|
|
133
|
+
if (Buffer.byteLength(value.task as string, "utf8") > MAX_V2_TASK_BYTES) {
|
|
134
|
+
return { ok: false, ...identity, error: "Delegation task exceeds 1 MiB when UTF-8 encoded." };
|
|
135
|
+
}
|
|
136
|
+
if (Buffer.byteLength(value.cwd as string, "utf8") > MAX_V2_CWD_BYTES) {
|
|
137
|
+
return { ok: false, ...identity, error: "Delegation cwd exceeds 32 KiB when UTF-8 encoded." };
|
|
138
|
+
}
|
|
139
|
+
if (Buffer.byteLength(value.agent as string, "utf8") > MAX_V2_SHORT_TEXT_BYTES) {
|
|
140
|
+
return { ok: false, ...identity, error: "Delegation agent exceeds 1 KiB when UTF-8 encoded." };
|
|
141
|
+
}
|
|
142
|
+
if (typeof value.model === "string" && Buffer.byteLength(value.model, "utf8") > MAX_V2_SHORT_TEXT_BYTES) {
|
|
143
|
+
return { ok: false, ...identity, error: "Delegation model exceeds 1 KiB when UTF-8 encoded." };
|
|
144
|
+
}
|
|
145
|
+
const skillEntries = typeof value.skill === "string" ? [value.skill] : Array.isArray(value.skill) ? value.skill as string[] : [];
|
|
146
|
+
if (skillEntries.length > MAX_V2_SKILL_ENTRIES) {
|
|
147
|
+
return { ok: false, ...identity, error: "Delegation skill supports at most 256 entries." };
|
|
148
|
+
}
|
|
149
|
+
if (skillEntries.some((entry) => Buffer.byteLength(entry, "utf8") > MAX_V2_SHORT_TEXT_BYTES)) {
|
|
150
|
+
return { ok: false, ...identity, error: "Delegation skill entry exceeds 1 KiB when UTF-8 encoded." };
|
|
151
|
+
}
|
|
152
|
+
if (skillEntries.reduce((total, entry) => total + Buffer.byteLength(entry, "utf8"), 0) > MAX_V2_SKILL_AGGREGATE_BYTES) {
|
|
153
|
+
return { ok: false, ...identity, error: "Delegation skill entries exceed 64 KiB in aggregate when UTF-8 encoded." };
|
|
154
|
+
}
|
|
155
|
+
if (value.thinking !== undefined && (typeof value.thinking !== "string" || !v2ThinkingLevels.has(value.thinking))) {
|
|
156
|
+
return { ok: false, ...identity, error: "thinking must be one of off, minimal, low, medium, high, xhigh, or max." };
|
|
157
|
+
}
|
|
158
|
+
if (!value.result || typeof value.result !== "object" || Array.isArray(value.result)) {
|
|
159
|
+
return { ok: false, ...identity, error: "result must be { kind: \"text\" } or { kind: \"structured\", schema: object }." };
|
|
160
|
+
}
|
|
161
|
+
const result = value.result as Record<string, unknown>;
|
|
162
|
+
let structuredSchema: Record<string, unknown> | undefined;
|
|
163
|
+
if (result.kind === "text") {
|
|
164
|
+
const unsupportedResultField = Object.keys(result).find((key) => key !== "kind");
|
|
165
|
+
if (unsupportedResultField) return { ok: false, ...identity, error: `result.${unsupportedResultField} is not supported for text results.` };
|
|
166
|
+
} else if (result.kind === "structured") {
|
|
167
|
+
const unsupportedResultField = Object.keys(result).find((key) => key !== "kind" && key !== "schema");
|
|
168
|
+
if (unsupportedResultField) return { ok: false, ...identity, error: `result.${unsupportedResultField} is not supported for structured results.` };
|
|
169
|
+
if (!result.schema || typeof result.schema !== "object" || Array.isArray(result.schema)) {
|
|
170
|
+
return { ok: false, ...identity, error: "result.schema must be a JSON Schema object." };
|
|
171
|
+
}
|
|
172
|
+
const inspectedSchema = cloneJsonWithinByteLimit(result.schema, MAX_SCHEMA_BYTES);
|
|
173
|
+
if (!inspectedSchema.ok) {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
...identity,
|
|
177
|
+
error: inspectedSchema.reason === "too_large"
|
|
178
|
+
? "result.schema exceeds 64 KiB when encoded."
|
|
179
|
+
: "result.schema must be plain JSON data.",
|
|
180
|
+
};
|
|
86
181
|
}
|
|
182
|
+
structuredSchema = inspectedSchema.value as Record<string, unknown>;
|
|
183
|
+
} else {
|
|
184
|
+
return { ok: false, ...identity, error: "result.kind must be text or structured." };
|
|
87
185
|
}
|
|
186
|
+
return {
|
|
187
|
+
ok: true,
|
|
188
|
+
request: {
|
|
189
|
+
...value,
|
|
190
|
+
result: structuredSchema
|
|
191
|
+
? { kind: "structured", schema: structuredSchema }
|
|
192
|
+
: { kind: "text" },
|
|
193
|
+
} as unknown as SubagentDelegationV2Request,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function parseV1(value: Record<string, unknown>, requestId: string): SubagentDelegationParseResult {
|
|
198
|
+
const unsupportedField = Object.keys(value).find((key) => !v1SupportedFields.has(key));
|
|
199
|
+
if (unsupportedField) return { ok: false, requestId, error: `Unsupported delegation field: ${unsupportedField}.` };
|
|
200
|
+
const sharedError = validateSharedFields(value, { requestId });
|
|
201
|
+
if (sharedError) return sharedError;
|
|
88
202
|
if (value.output !== undefined && typeof value.output !== "boolean" && !nonEmptyString(value.output)) {
|
|
89
203
|
return { ok: false, requestId, error: "output must be a boolean or non-empty string." };
|
|
90
204
|
}
|
|
@@ -94,10 +208,42 @@ export function parseSubagentDelegationRequest(data: unknown): SubagentDelegatio
|
|
|
94
208
|
if (value.outputMode === "file-only" && !nonEmptyString(value.output)) {
|
|
95
209
|
return { ok: false, requestId, error: 'outputMode "file-only" requires output to be a non-empty path.' };
|
|
96
210
|
}
|
|
211
|
+
if (value.outputSchema !== undefined && (!value.outputSchema || typeof value.outputSchema !== "object" || Array.isArray(value.outputSchema))) {
|
|
212
|
+
return { ok: false, requestId, error: "outputSchema must be a JSON Schema object." };
|
|
213
|
+
}
|
|
214
|
+
if (value.agentContract !== undefined) {
|
|
215
|
+
if (!value.agentContract || typeof value.agentContract !== "object" || Array.isArray(value.agentContract)) return { ok: false, requestId, error: "agentContract must be an object." };
|
|
216
|
+
const contract = value.agentContract as Record<string, unknown>;
|
|
217
|
+
if (contract.version !== 1 || Object.keys(contract).some((key) => key !== "version")) return { ok: false, requestId, error: "agentContract must be { version: 1 }." };
|
|
218
|
+
}
|
|
97
219
|
const acceptanceErrors = validateAcceptanceInput(value.acceptance);
|
|
98
220
|
if (acceptanceErrors.length > 0) return { ok: false, requestId, error: acceptanceErrors.join(" ") };
|
|
99
|
-
if (value.artifacts !== undefined && typeof value.artifacts !== "boolean") {
|
|
100
|
-
return { ok: false, requestId, error: "artifacts must be a boolean." };
|
|
101
|
-
}
|
|
102
221
|
return { ok: true, request: value as unknown as SubagentDelegationRequest };
|
|
103
222
|
}
|
|
223
|
+
|
|
224
|
+
export function parseSubagentDelegationRequest(data: unknown): SubagentDelegationParseResult {
|
|
225
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
226
|
+
return { ok: false, error: "Delegation request must be an object." };
|
|
227
|
+
}
|
|
228
|
+
const value = data as Record<string, unknown>;
|
|
229
|
+
const requestId = validateId(value.requestId);
|
|
230
|
+
if (value.version !== SUBAGENT_DELEGATION_PROTOCOL_VERSION && value.version !== SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION) {
|
|
231
|
+
return {
|
|
232
|
+
...(requestId ? { requestId } : {}),
|
|
233
|
+
ok: false,
|
|
234
|
+
error: `Unsupported delegation protocol version: ${String(value.version)}.`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (!requestId) {
|
|
238
|
+
return { ok: false, error: "Delegation requestId must be a non-empty string of at most 256 characters without newlines." };
|
|
239
|
+
}
|
|
240
|
+
if (value.version === SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION) {
|
|
241
|
+
// Preserve the v1 parser's historical diagnostic for a v1-shaped payload
|
|
242
|
+
// whose version alone was changed to 2.
|
|
243
|
+
if (!Object.hasOwn(value, "ownerRunId") && !Object.hasOwn(value, "nodeId") && !Object.hasOwn(value, "result")) {
|
|
244
|
+
return { ok: false, version: 2, requestId, error: "Unsupported delegation protocol version: 2." };
|
|
245
|
+
}
|
|
246
|
+
return parseV2(value, requestId);
|
|
247
|
+
}
|
|
248
|
+
return parseV1(value, requestId);
|
|
249
|
+
}
|