@jini-ai/agent-runtime 0.3.10 → 0.4.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 +103 -0
- package/README.md +34 -17
- package/dist/acp-model-probe.d.ts +6 -4
- package/dist/acp-model-probe.js +3 -3
- package/dist/agent-protocol/acp/account-failure.d.ts +4 -2
- package/dist/agent-protocol/acp/account-failure.js +1 -1
- package/dist/agent-protocol/acp/json.d.ts +6 -6
- package/dist/agent-protocol/acp/json.js +3 -3
- package/dist/agent-protocol/acp/models.d.ts +10 -5
- package/dist/agent-protocol/acp/models.js +32 -31
- package/dist/agent-protocol/acp/session-params.d.ts +3 -1
- package/dist/agent-protocol/acp/session-params.js +2 -1
- package/dist/agent-protocol/acp/session.d.ts +28 -22
- package/dist/agent-protocol/acp/session.js +181 -147
- package/dist/agent-protocol/acp/text-suppression.d.ts +1 -1
- package/dist/agent-protocol/acp/text-suppression.js +1 -1
- package/dist/agent-protocol/acp/types.d.ts +3 -2
- package/dist/agent-protocol/acp/updates.d.ts +11 -11
- package/dist/agent-protocol/acp/updates.js +2 -2
- package/dist/agent-protocol/core/json-line-stream.d.ts +24 -8
- package/dist/agent-protocol/core/json-line-stream.js +8 -8
- package/dist/agent-protocol/pi-rpc/events.d.ts +5 -1
- package/dist/agent-protocol/pi-rpc/events.js +36 -36
- package/dist/agent-protocol/pi-rpc/internal.d.ts +4 -1
- package/dist/agent-protocol/pi-rpc/models.d.ts +3 -1
- package/dist/agent-protocol/pi-rpc/models.js +1 -1
- package/dist/agent-protocol/pi-rpc/session.d.ts +1 -1
- package/dist/agent-protocol/pi-rpc/session.js +33 -32
- package/dist/amr-model-cache.d.ts +12 -4
- package/dist/amr-model-cache.js +17 -21
- package/dist/amr-profile-resolver.d.ts +3 -1
- package/dist/anthropic-live-models.js +0 -0
- package/dist/artifact-taxonomy.d.ts +6 -2
- package/dist/auth.d.ts +52 -15
- package/dist/auth.js +36 -36
- package/dist/claude-code-models.js +2 -2
- package/dist/claude-stream.d.ts +22 -3
- package/dist/claude-stream.js +24 -8
- package/dist/copilot-stream.d.ts +7 -3
- package/dist/copilot-stream.js +3 -3
- package/dist/defs/aider.d.ts +8 -1
- package/dist/defs/aider.js +2 -2
- package/dist/defs/amp.d.ts +8 -1
- package/dist/defs/amp.js +2 -2
- package/dist/defs/amr.d.ts +22 -6
- package/dist/defs/amr.js +23 -23
- package/dist/defs/antigravity.d.ts +15 -4
- package/dist/defs/antigravity.js +9 -8
- package/dist/defs/auggie.d.ts +4 -1
- package/dist/defs/auggie.js +1 -1
- package/dist/defs/claude.d.ts +12 -2
- package/dist/defs/claude.js +5 -5
- package/dist/defs/cline.d.ts +4 -1
- package/dist/defs/cline.js +1 -1
- package/dist/defs/codebuddy.d.ts +8 -1
- package/dist/defs/codebuddy.js +2 -2
- package/dist/defs/codex.d.ts +18 -4
- package/dist/defs/codex.js +7 -7
- package/dist/defs/copilot.d.ts +8 -1
- package/dist/defs/copilot.js +2 -2
- package/dist/defs/crush.d.ts +8 -1
- package/dist/defs/crush.js +1 -1
- package/dist/defs/cursor-agent.d.ts +14 -3
- package/dist/defs/cursor-agent.js +5 -5
- package/dist/defs/deepseek.d.ts +8 -1
- package/dist/defs/deepseek.js +2 -2
- package/dist/defs/devin.d.ts +12 -2
- package/dist/defs/devin.js +3 -3
- package/dist/defs/droid.d.ts +8 -1
- package/dist/defs/droid.js +1 -1
- package/dist/defs/gemini.d.ts +8 -1
- package/dist/defs/gemini.js +1 -1
- package/dist/defs/goose.d.ts +4 -1
- package/dist/defs/goose.js +1 -1
- package/dist/defs/grok-build.d.ts +11 -2
- package/dist/defs/grok-build.js +3 -3
- package/dist/defs/hermes.d.ts +4 -1
- package/dist/defs/hermes.js +2 -2
- package/dist/defs/kilo.d.ts +4 -1
- package/dist/defs/kilo.js +2 -2
- package/dist/defs/kimi.d.ts +4 -1
- package/dist/defs/kimi.js +2 -2
- package/dist/defs/kiro.d.ts +4 -1
- package/dist/defs/kiro.js +2 -2
- package/dist/defs/mimo.d.ts +8 -1
- package/dist/defs/mimo.js +2 -2
- package/dist/defs/opencode.d.ts +9 -2
- package/dist/defs/opencode.js +2 -2
- package/dist/defs/pi.d.ts +12 -2
- package/dist/defs/pi.js +9 -9
- package/dist/defs/qoder.d.ts +8 -1
- package/dist/defs/qoder.js +2 -2
- package/dist/defs/qwen.d.ts +8 -1
- package/dist/defs/qwen.js +2 -2
- package/dist/defs/reasonix.d.ts +4 -1
- package/dist/defs/reasonix.js +3 -3
- package/dist/defs/shared.d.ts +4 -2
- package/dist/defs/shared.js +2 -2
- package/dist/defs/trae-cli.d.ts +12 -2
- package/dist/defs/trae-cli.js +3 -3
- package/dist/defs/vibe.d.ts +4 -1
- package/dist/defs/vibe.js +2 -2
- package/dist/detection.d.ts +19 -4
- package/dist/detection.js +33 -33
- package/dist/diagnostics.d.ts +14 -3
- package/dist/diagnostics.js +5 -5
- package/dist/env.d.ts +16 -4
- package/dist/env.js +7 -7
- package/dist/executables.d.ts +24 -8
- package/dist/executables.js +13 -13
- package/dist/index.d.ts +228 -4
- package/dist/index.js +230 -6
- package/dist/invocation.d.ts +6 -1
- package/dist/invocation.js +2 -2
- package/dist/json-event-stream.d.ts +7 -2
- package/dist/json-event-stream.js +3 -3
- package/dist/launch.d.ts +12 -2
- package/dist/launch.js +3 -3
- package/dist/log-file.d.ts +4 -1
- package/dist/log-file.js +2 -2
- package/dist/mcp.d.ts +6 -3
- package/dist/mcp.js +1 -1
- package/dist/metadata.d.ts +7 -3
- package/dist/metadata.js +1 -1
- package/dist/mmd-routes.d.ts +22 -6
- package/dist/mmd-routes.js +14 -14
- package/dist/model-catalog-cache.d.ts +50 -0
- package/dist/model-catalog-cache.js +61 -0
- package/dist/model-loading-state.d.ts +10 -0
- package/dist/model-loading-state.js +23 -0
- package/dist/model-registry.d.ts +28 -7
- package/dist/model-registry.js +11 -8
- package/dist/models.d.ts +31 -6
- package/dist/models.js +6 -6
- package/dist/opencode-log.d.ts +12 -4
- package/dist/opencode-log.js +8 -8
- package/dist/paths.d.ts +6 -2
- package/dist/paths.js +3 -3
- package/dist/pi-models.d.ts +3 -1
- package/dist/pi-models.js +2 -2
- package/dist/prompt-budget.d.ts +17 -4
- package/dist/prompt-budget.js +3 -3
- package/dist/prompt-file.d.ts +5 -1
- package/dist/prompt-file.js +1 -1
- package/dist/providers/aihubmix.d.ts +34 -9
- package/dist/providers/aihubmix.js +26 -26
- package/dist/providers/anthropic-messages.d.ts +77 -20
- package/dist/providers/anthropic-messages.js +53 -50
- package/dist/providers/azure-chat.d.ts +21 -7
- package/dist/providers/azure-chat.js +18 -16
- package/dist/providers/connection-guard.d.ts +45 -31
- package/dist/providers/connection-guard.js +40 -143
- package/dist/providers/connection-test.d.ts +1 -1
- package/dist/providers/connection-test.js +13 -30
- package/dist/providers/elevenlabs.d.ts +7 -2
- package/dist/providers/elevenlabs.js +4 -4
- package/dist/providers/google-messages.d.ts +44 -10
- package/dist/providers/google-messages.js +36 -34
- package/dist/providers/google-schema.d.ts +26 -0
- package/dist/providers/google-schema.js +253 -0
- package/dist/providers/google.d.ts +21 -6
- package/dist/providers/google.js +10 -10
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.js +3 -2
- package/dist/providers/model-catalog.d.ts +1 -1
- package/dist/providers/model-catalog.js +14 -27
- package/dist/providers/oauth-callback-server.d.ts +1 -1
- package/dist/providers/oauth-callback-server.js +3 -2
- package/dist/providers/oauth-credentials.d.ts +7 -1
- package/dist/providers/oauth-credentials.js +5 -5
- package/dist/providers/oauth-provider.d.ts +2 -2
- package/dist/providers/oauth-provider.js +20 -25
- package/dist/providers/oauth-tokens.d.ts +26 -6
- package/dist/providers/oauth-tokens.js +9 -9
- package/dist/providers/ollama-chat.d.ts +66 -31
- package/dist/providers/ollama-chat.js +64 -61
- package/dist/providers/openai-chat.d.ts +77 -22
- package/dist/providers/openai-chat.js +71 -67
- package/dist/providers/pkce.d.ts +33 -20
- package/dist/providers/pkce.js +75 -146
- package/dist/providers/sse-decode.d.ts +3 -1
- package/dist/providers/sse-decode.js +1 -1
- package/dist/providers/token-params.d.ts +16 -5
- package/dist/providers/token-params.js +6 -6
- package/dist/providers/tool-turn-types.d.ts +89 -0
- package/dist/providers/tool-turn-types.js +2 -0
- package/dist/providers/tool-turn.d.ts +10 -0
- package/dist/providers/tool-turn.js +252 -0
- package/dist/qoder-stream.d.ts +7 -3
- package/dist/qoder-stream.js +3 -3
- package/dist/registry.d.ts +6 -2
- package/dist/registry.js +4 -4
- package/dist/resolution.d.ts +5 -1
- package/dist/resolution.js +3 -3
- package/dist/role-marker-guard.d.ts +9 -5
- package/dist/role-marker-guard.js +5 -5
- package/dist/terminal-launch.d.ts +6 -1
- package/dist/terminal-launch.js +2 -2
- package/dist/types.d.ts +22 -6
- package/package.json +43 -4
|
@@ -107,9 +107,9 @@ export function handleThoughtChunkUpdate({ state, effects, update, }) {
|
|
|
107
107
|
return;
|
|
108
108
|
if (!state.emittedThinkingStart) {
|
|
109
109
|
state.emittedThinkingStart = true;
|
|
110
|
-
effects.send('agent', { type: 'thinking_start' });
|
|
110
|
+
effects.send({ event: 'agent', payload: { type: 'thinking_start' } });
|
|
111
111
|
}
|
|
112
|
-
effects.send('agent', { type: 'thinking_delta', delta: text });
|
|
112
|
+
effects.send({ event: 'agent', payload: { type: 'thinking_delta', delta: text } });
|
|
113
113
|
}
|
|
114
114
|
/** Strips tool-call XML from a raw message delta via the session's tool-call text suppressor, noting the suppression reason. */
|
|
115
115
|
export function stripToolCallText({ effects, delta }) {
|
|
@@ -251,13 +251,13 @@ export function mirrorArtifactWriteToolEvent({ state, effects, update, toolCallI
|
|
|
251
251
|
if (!shouldEmit)
|
|
252
252
|
return;
|
|
253
253
|
st.emitted = true;
|
|
254
|
-
effects.send('agent', {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
effects.send('agent', { type: 'tool_result', toolUseId: toolCallId, isError: failed });
|
|
254
|
+
effects.send({ event: 'agent', payload: {
|
|
255
|
+
type: 'tool_use',
|
|
256
|
+
id: toolCallId,
|
|
257
|
+
name: 'Write',
|
|
258
|
+
input: { file_path: st.path ?? toolCallId },
|
|
259
|
+
} });
|
|
260
|
+
effects.send({ event: 'agent', payload: { type: 'tool_result', toolUseId: toolCallId, isError: failed } });
|
|
261
261
|
state.emittedConcreteToolEvent = true;
|
|
262
262
|
}
|
|
263
263
|
/** Arms the DSML artifact suppressor when a tool_call/tool_call_update completes an artifact write, or disarms it once its owning write call terminally fails. */
|
|
@@ -302,11 +302,11 @@ export function handleSessionUpdate({ state, effects, update, }) {
|
|
|
302
302
|
if (effects.modelUnavailableErrorCode && tryPromoteAmrRetryStatus({ effects, update }))
|
|
303
303
|
return;
|
|
304
304
|
if (update.sessionUpdate !== 'agent_message_chunk' && update.sessionUpdate !== 'agent_thought_chunk') {
|
|
305
|
-
effects.send('agent', {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
305
|
+
effects.send({ event: 'agent', payload: {
|
|
306
|
+
type: 'status',
|
|
307
|
+
label: String(update.sessionUpdate || 'session_update'),
|
|
308
|
+
elapsedMs: Date.now() - effects.runStartedAt,
|
|
309
|
+
} });
|
|
310
310
|
effects.emitAcpRawShapeDiagnostic(update);
|
|
311
311
|
}
|
|
312
312
|
if (update.sessionUpdate === 'agent_thought_chunk') {
|
|
@@ -329,7 +329,7 @@ export function handleInitializeAck({ state, effects }) {
|
|
|
329
329
|
effects.writeRpc(state.nextId, 'session/load', { sessionId: effects.resumeSessionId, cwd: effects.effectiveCwd }, 'session/load');
|
|
330
330
|
}
|
|
331
331
|
else {
|
|
332
|
-
effects.writeRpc(state.nextId, 'session/new', buildAcpSessionNewParams(effects.effectiveCwd, effects.mcpServers ? { mcpServers: effects.mcpServers, envFormat: effects.envFormat } : { envFormat: effects.envFormat }), 'session/new');
|
|
332
|
+
effects.writeRpc(state.nextId, 'session/new', buildAcpSessionNewParams({ cwd: effects.effectiveCwd }, effects.mcpServers ? { mcpServers: effects.mcpServers, envFormat: effects.envFormat } : { envFormat: effects.envFormat }), 'session/new');
|
|
333
333
|
}
|
|
334
334
|
state.nextId += 1;
|
|
335
335
|
}
|
|
@@ -358,7 +358,7 @@ export function handleSessionNewAck({ state, effects, result, rawLine, }) {
|
|
|
358
358
|
state.modelConfigId = resolveModelConfigId(findModelConfigOption(result.configOptions));
|
|
359
359
|
state.activeModel = currentModelFromSessionResult(result);
|
|
360
360
|
if (state.sessionId && state.activeModel) {
|
|
361
|
-
effects.send('agent', { type: 'status', label: 'model', model: state.activeModel });
|
|
361
|
+
effects.send({ event: 'agent', payload: { type: 'status', label: 'model', model: state.activeModel } });
|
|
362
362
|
}
|
|
363
363
|
if (triggerSetModelIfNeeded({ state, effects }))
|
|
364
364
|
return;
|
|
@@ -403,7 +403,7 @@ export function handleModelSetAck({ state, effects, result, }) {
|
|
|
403
403
|
// across the function boundary for the type checker (it doesn't survive
|
|
404
404
|
// the call), not a behavior change.
|
|
405
405
|
state.activeModel = currentModelFromSessionResult(result) ?? effects.model;
|
|
406
|
-
effects.send('agent', { type: 'status', label: 'model', model: state.activeModel });
|
|
406
|
+
effects.send({ event: 'agent', payload: { type: 'status', label: 'model', model: state.activeModel } });
|
|
407
407
|
effects.sendPrompt();
|
|
408
408
|
}
|
|
409
409
|
/** Top-level JSON-RPC result dispatcher, keyed by which request `expectedId`/`promptRequestId` is currently outstanding. */
|
|
@@ -462,13 +462,19 @@ function resolveAcpSessionOptionDefaults(options) {
|
|
|
462
462
|
* @returns A controller with `hasFatalError`, `getDurableSessionId`,
|
|
463
463
|
* `completedSuccessfully`, and `abort` methods.
|
|
464
464
|
*/
|
|
465
|
-
export function attachAcpSession(
|
|
465
|
+
export function attachAcpSession(requiredArgs, optionalArgs = {}) {
|
|
466
|
+
const options = { ...optionalArgs, ...requiredArgs };
|
|
466
467
|
const { child, prompt, cwd, model, mcpServers, send, modelUnavailableErrorCode, resumeSessionId, onPermissionRequest, onCliReady, onSessionInit, } = options;
|
|
467
468
|
const { imagePaths, envFormat, clientName, clientVersion, stageTimeoutMs, executionProfile, accountFailureClassifier, } = resolveAcpSessionOptionDefaults(options);
|
|
468
469
|
const runStartedAt = Date.now();
|
|
469
470
|
const effectiveCwd = path.resolve(cwd || process.cwd());
|
|
470
471
|
if (!child.stdin || !child.stdout) {
|
|
471
|
-
|
|
472
|
+
const error = new Error('ACP child process must expose stdin and stdout streams');
|
|
473
|
+
if (resumeSessionId)
|
|
474
|
+
Object.assign(error, {
|
|
475
|
+
details: { kind: 'resume_failed', sessionId: resumeSessionId },
|
|
476
|
+
});
|
|
477
|
+
throw error;
|
|
472
478
|
}
|
|
473
479
|
const stdin = child.stdin;
|
|
474
480
|
const stdout = child.stdout;
|
|
@@ -531,6 +537,34 @@ export function attachAcpSession(options) {
|
|
|
531
537
|
value.includes('unknown model') ||
|
|
532
538
|
value.includes('invalid model'));
|
|
533
539
|
};
|
|
540
|
+
/** Emits a failure, tagging an unacknowledged resume without losing its cause.
|
|
541
|
+
* The session id is only set by a valid load acknowledgement, so the same
|
|
542
|
+
* boundary covers initialize/load RPC errors, watchdogs, and transport loss.
|
|
543
|
+
* Fresh sessions and errors after load keep their existing payloads.
|
|
544
|
+
* @complexity O(1), excluding the host's event sink.
|
|
545
|
+
*/
|
|
546
|
+
const sendFailure = ({ payload }) => {
|
|
547
|
+
if (!resumeSessionId || state.sessionId) {
|
|
548
|
+
send({ event: 'error', payload });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const original = asObject(payload) ?? {};
|
|
552
|
+
const originalError = asObject(original.error);
|
|
553
|
+
send({ event: 'error', payload: {
|
|
554
|
+
...original,
|
|
555
|
+
error: {
|
|
556
|
+
code: 'AGENT_EXECUTION_FAILED',
|
|
557
|
+
message: original.message,
|
|
558
|
+
retryable: false,
|
|
559
|
+
...originalError,
|
|
560
|
+
details: {
|
|
561
|
+
kind: 'resume_failed',
|
|
562
|
+
sessionId: resumeSessionId,
|
|
563
|
+
...(originalError?.details === undefined ? {} : { cause: originalError.details }),
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
} });
|
|
567
|
+
};
|
|
534
568
|
// Both call sites below (the session/update retry-status promotion and the
|
|
535
569
|
// stderr promotion) are already nested inside a scope gated by `finished`
|
|
536
570
|
// at their own entry point (the parser callback's own `if (aborted ||
|
|
@@ -540,12 +574,12 @@ export function attachAcpSession(options) {
|
|
|
540
574
|
// second time once `finished` is true. The origin file carried its own
|
|
541
575
|
// `if (finished) return;` re-entry guard here anyway; removed per this
|
|
542
576
|
// package's coverage-driven dead-branch discipline (verified unreachable
|
|
543
|
-
// by tracing every call site — see
|
|
577
|
+
// by tracing every call site — see archived provenance ledger), not suppressed.
|
|
544
578
|
const failWithPayload = (payload) => {
|
|
545
579
|
finished = true;
|
|
546
580
|
fatal = true;
|
|
547
581
|
clearStageTimer();
|
|
548
|
-
|
|
582
|
+
sendFailure({ payload });
|
|
549
583
|
if (!child.killed)
|
|
550
584
|
child.kill('SIGTERM');
|
|
551
585
|
};
|
|
@@ -557,29 +591,29 @@ export function attachAcpSession(options) {
|
|
|
557
591
|
clearStageTimer();
|
|
558
592
|
const useModelUnavailable = modelUnavailableErrorCode &&
|
|
559
593
|
(options.forceModelUnavailable || isModelUnavailableError(message));
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
message,
|
|
566
|
-
error: {
|
|
567
|
-
code: 'AGENT_EXECUTION_FAILED',
|
|
594
|
+
sendFailure({ payload: useModelUnavailable
|
|
595
|
+
? amrModelUnavailablePayload(message)
|
|
596
|
+
: options.details === undefined && options.retryable === undefined
|
|
597
|
+
? { message }
|
|
598
|
+
: {
|
|
568
599
|
message,
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
600
|
+
error: {
|
|
601
|
+
code: 'AGENT_EXECUTION_FAILED',
|
|
602
|
+
message,
|
|
603
|
+
retryable: options.retryable ?? false,
|
|
604
|
+
// Every real call site that reaches this branch (the
|
|
605
|
+
// RPC-error path and the no-visible-output path) always
|
|
606
|
+
// supplies `details` whenever it supplies `retryable`, and
|
|
607
|
+
// `retryable` is itself derived from the same `details`
|
|
608
|
+
// source at the RPC-error call site — so `options.details`
|
|
609
|
+
// is never actually undefined here. Spreading it
|
|
610
|
+
// unconditionally (rather than conditionally omitting the
|
|
611
|
+
// key) is behaviorally identical for every real caller;
|
|
612
|
+
// simplified per this package's coverage-driven
|
|
613
|
+
// dead-branch discipline. See archived provenance ledger.
|
|
614
|
+
details: options.details,
|
|
615
|
+
},
|
|
616
|
+
} });
|
|
583
617
|
if (!child.killed)
|
|
584
618
|
child.kill('SIGTERM');
|
|
585
619
|
};
|
|
@@ -598,13 +632,13 @@ export function attachAcpSession(options) {
|
|
|
598
632
|
if (rawAcpShapeDiagnosticCount >= ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT)
|
|
599
633
|
return;
|
|
600
634
|
rawAcpShapeDiagnosticCount += 1;
|
|
601
|
-
send('agent', {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
635
|
+
send({ event: 'agent', payload: {
|
|
636
|
+
type: 'diagnostic',
|
|
637
|
+
name: 'acp_raw_event_shape',
|
|
638
|
+
source: 'acp-json-rpc',
|
|
639
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
640
|
+
shape: acpRawEventShape(update),
|
|
641
|
+
} });
|
|
608
642
|
};
|
|
609
643
|
// All three call sites guard against an empty `delta` before calling this
|
|
610
644
|
// (the finishCleanPrompt flush, the `if (outputDelta)` branch, and the
|
|
@@ -613,18 +647,18 @@ export function attachAcpSession(options) {
|
|
|
613
647
|
// origin file's own `if (!delta) return;` guard here can never actually
|
|
614
648
|
// fire. Removed per this package's coverage-driven dead-branch
|
|
615
649
|
// discipline (verified unreachable by tracing every call site — see
|
|
616
|
-
//
|
|
650
|
+
// archived provenance ledger), not suppressed.
|
|
617
651
|
const emitVisibleTextDelta = (delta) => {
|
|
618
652
|
state.emittedVisibleTextChunk = true;
|
|
619
653
|
if (!emittedFirstTokenStatus) {
|
|
620
654
|
emittedFirstTokenStatus = true;
|
|
621
|
-
send('agent', {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
655
|
+
send({ event: 'agent', payload: {
|
|
656
|
+
type: 'status',
|
|
657
|
+
label: 'streaming',
|
|
658
|
+
ttftMs: Date.now() - runStartedAt,
|
|
659
|
+
} });
|
|
626
660
|
}
|
|
627
|
-
send('agent', { type: 'text_delta', delta });
|
|
661
|
+
send({ event: 'agent', payload: { type: 'text_delta', delta } });
|
|
628
662
|
};
|
|
629
663
|
const noteArtifactTextSuppression = (reason) => {
|
|
630
664
|
if (!state.dsmlArtifactSuppressor)
|
|
@@ -641,42 +675,42 @@ export function attachAcpSession(options) {
|
|
|
641
675
|
if (artifactSuppressionDiagnosticCount >= ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT)
|
|
642
676
|
return;
|
|
643
677
|
artifactSuppressionDiagnosticCount += 1;
|
|
644
|
-
send('agent', {
|
|
645
|
-
type: 'diagnostic',
|
|
646
|
-
name: 'acp_artifact_text_suppression',
|
|
647
|
-
source: 'acp-json-rpc',
|
|
648
|
-
elapsedMs: Date.now() - runStartedAt,
|
|
649
|
-
reason,
|
|
650
|
-
suppressedChars: artifactTextSuppressionSummary.suppressedChars,
|
|
651
|
-
suppressedChunks: artifactTextSuppressionSummary.suppressedChunks,
|
|
652
|
-
openedBlocks: artifactTextSuppressionSummary.openedBlocks,
|
|
653
|
-
closedBlocks: artifactTextSuppressionSummary.closedBlocks,
|
|
654
|
-
pendingCandidateChars: stats.pendingCandidateChars,
|
|
655
|
-
suppressing: stats.suppressing,
|
|
656
|
-
});
|
|
657
|
-
};
|
|
658
|
-
const emitArtifactTextSuppressionSummary = () => {
|
|
659
|
-
if (artifactTextSuppressionSummary.suppressedChars <= 0)
|
|
660
|
-
return;
|
|
661
|
-
if (executionProfile === 'filesystem') {
|
|
662
|
-
send('agent', {
|
|
678
|
+
send({ event: 'agent', payload: {
|
|
663
679
|
type: 'diagnostic',
|
|
664
|
-
name: '
|
|
680
|
+
name: 'acp_artifact_text_suppression',
|
|
665
681
|
source: 'acp-json-rpc',
|
|
666
682
|
elapsedMs: Date.now() - runStartedAt,
|
|
683
|
+
reason,
|
|
667
684
|
suppressedChars: artifactTextSuppressionSummary.suppressedChars,
|
|
668
685
|
suppressedChunks: artifactTextSuppressionSummary.suppressedChunks,
|
|
669
686
|
openedBlocks: artifactTextSuppressionSummary.openedBlocks,
|
|
670
687
|
closedBlocks: artifactTextSuppressionSummary.closedBlocks,
|
|
671
|
-
|
|
688
|
+
pendingCandidateChars: stats.pendingCandidateChars,
|
|
689
|
+
suppressing: stats.suppressing,
|
|
690
|
+
} });
|
|
691
|
+
};
|
|
692
|
+
const emitArtifactTextSuppressionSummary = () => {
|
|
693
|
+
if (artifactTextSuppressionSummary.suppressedChars <= 0)
|
|
694
|
+
return;
|
|
695
|
+
if (executionProfile === 'filesystem') {
|
|
696
|
+
send({ event: 'agent', payload: {
|
|
697
|
+
type: 'diagnostic',
|
|
698
|
+
name: 'unexpected_text_artifact_in_filesystem_run',
|
|
699
|
+
source: 'acp-json-rpc',
|
|
700
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
701
|
+
suppressedChars: artifactTextSuppressionSummary.suppressedChars,
|
|
702
|
+
suppressedChunks: artifactTextSuppressionSummary.suppressedChunks,
|
|
703
|
+
openedBlocks: artifactTextSuppressionSummary.openedBlocks,
|
|
704
|
+
closedBlocks: artifactTextSuppressionSummary.closedBlocks,
|
|
705
|
+
} });
|
|
672
706
|
}
|
|
673
|
-
send('agent', {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
707
|
+
send({ event: 'agent', payload: {
|
|
708
|
+
type: 'diagnostic',
|
|
709
|
+
name: 'acp_artifact_text_suppression_summary',
|
|
710
|
+
source: 'acp-json-rpc',
|
|
711
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
712
|
+
...artifactTextSuppressionSummary,
|
|
713
|
+
} });
|
|
680
714
|
};
|
|
681
715
|
const noteToolCallTextSuppression = (reason) => {
|
|
682
716
|
const stats = toolCallTextSuppressor.stats();
|
|
@@ -691,30 +725,30 @@ export function attachAcpSession(options) {
|
|
|
691
725
|
if (artifactSuppressionDiagnosticCount >= ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT)
|
|
692
726
|
return;
|
|
693
727
|
artifactSuppressionDiagnosticCount += 1;
|
|
694
|
-
send('agent', {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
728
|
+
send({ event: 'agent', payload: {
|
|
729
|
+
type: 'diagnostic',
|
|
730
|
+
name: 'acp_tool_call_text_suppression',
|
|
731
|
+
source: 'acp-json-rpc',
|
|
732
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
733
|
+
reason,
|
|
734
|
+
suppressedChars: toolCallTextSuppressionSummary.suppressedChars,
|
|
735
|
+
suppressedChunks: toolCallTextSuppressionSummary.suppressedChunks,
|
|
736
|
+
openedBlocks: toolCallTextSuppressionSummary.openedBlocks,
|
|
737
|
+
closedBlocks: toolCallTextSuppressionSummary.closedBlocks,
|
|
738
|
+
pendingCandidateChars: stats.pendingCandidateChars,
|
|
739
|
+
suppressing: stats.suppressing,
|
|
740
|
+
} });
|
|
707
741
|
};
|
|
708
742
|
const emitToolCallTextSuppressionSummary = () => {
|
|
709
743
|
if (toolCallTextSuppressionSummary.suppressedChars <= 0)
|
|
710
744
|
return;
|
|
711
|
-
send('agent', {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
745
|
+
send({ event: 'agent', payload: {
|
|
746
|
+
type: 'diagnostic',
|
|
747
|
+
name: 'acp_tool_call_text_suppression_summary',
|
|
748
|
+
source: 'acp-json-rpc',
|
|
749
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
750
|
+
...toolCallTextSuppressionSummary,
|
|
751
|
+
} });
|
|
718
752
|
};
|
|
719
753
|
const sendPrompt = () => {
|
|
720
754
|
state.promptRequestId = state.nextId;
|
|
@@ -723,11 +757,11 @@ export function attachAcpSession(options) {
|
|
|
723
757
|
sessionId: state.sessionId,
|
|
724
758
|
prompt: buildPromptBlocks(prompt, imagePaths),
|
|
725
759
|
}, 'session/prompt');
|
|
726
|
-
send('agent', {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
760
|
+
send({ event: 'agent', payload: {
|
|
761
|
+
type: 'status',
|
|
762
|
+
label: 'waiting_for_first_output',
|
|
763
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
764
|
+
} });
|
|
731
765
|
state.nextId += 1;
|
|
732
766
|
};
|
|
733
767
|
// `finishCleanPrompt` has exactly one call site (`handlePromptResult`,
|
|
@@ -736,7 +770,7 @@ export function attachAcpSession(options) {
|
|
|
736
770
|
// (aborted || finished) return;` guard at entry — so this function can
|
|
737
771
|
// never actually be invoked once `finished` is true. The origin file
|
|
738
772
|
// carried its own re-entry guard here anyway; removed per this package's
|
|
739
|
-
// coverage-driven dead-branch discipline. See
|
|
773
|
+
// coverage-driven dead-branch discipline. See archived provenance ledger.
|
|
740
774
|
const finishCleanPrompt = (usageSource) => {
|
|
741
775
|
const flushedToolText = toolCallTextSuppressor.flush();
|
|
742
776
|
noteToolCallTextSuppression('tool_call_xml_flush');
|
|
@@ -751,11 +785,11 @@ export function attachAcpSession(options) {
|
|
|
751
785
|
emitArtifactTextSuppressionSummary();
|
|
752
786
|
const usage = formatUsage(usageSource);
|
|
753
787
|
if (usage) {
|
|
754
|
-
send('agent', {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
788
|
+
send({ event: 'agent', payload: {
|
|
789
|
+
type: 'usage',
|
|
790
|
+
usage,
|
|
791
|
+
durationMs: Date.now() - runStartedAt,
|
|
792
|
+
} });
|
|
759
793
|
}
|
|
760
794
|
finished = true;
|
|
761
795
|
clearStageTimer();
|
|
@@ -827,7 +861,7 @@ export function attachAcpSession(options) {
|
|
|
827
861
|
const recoverFromModelSelectionError = () => {
|
|
828
862
|
state.setModelRequestId = null;
|
|
829
863
|
state.activeModel = state.activeModel || 'default';
|
|
830
|
-
send('agent', { type: 'status', label: 'model', model: state.activeModel });
|
|
864
|
+
send({ event: 'agent', payload: { type: 'status', label: 'model', model: state.activeModel } });
|
|
831
865
|
sendPrompt();
|
|
832
866
|
};
|
|
833
867
|
const effects = {
|
|
@@ -853,36 +887,36 @@ export function attachAcpSession(options) {
|
|
|
853
887
|
mcpServers,
|
|
854
888
|
envFormat,
|
|
855
889
|
};
|
|
856
|
-
const parser = createJsonLineStream((raw, rawLine) => {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
stdout.on('data', (chunk) => parser.feed(chunk));
|
|
890
|
+
const parser = createJsonLineStream({ onMessage: ({ message: raw, rawLine }) => {
|
|
891
|
+
if (aborted || finished)
|
|
892
|
+
return;
|
|
893
|
+
resetStageTimer('response');
|
|
894
|
+
const obj = asObject(raw);
|
|
895
|
+
if (!obj)
|
|
896
|
+
return;
|
|
897
|
+
// First well-formed ACP JSON-RPC message = CLI ready. Caller dedupes, so
|
|
898
|
+
// re-notifying on later messages is harmless.
|
|
899
|
+
onCliReady?.();
|
|
900
|
+
const error = asObject(obj.error);
|
|
901
|
+
const params = asObject(obj.params);
|
|
902
|
+
const result = asObject(obj.result);
|
|
903
|
+
const rpcErr = rpcErrorMessage(obj);
|
|
904
|
+
if (rpcErr) {
|
|
905
|
+
handleRpcError({ state, effects, obj, error, rpcErr });
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (obj.method === 'session/request_permission') {
|
|
909
|
+
replyPermission(obj);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
const update = asObject(params?.update);
|
|
913
|
+
if (obj.method === 'session/update' && update) {
|
|
914
|
+
handleSessionUpdate({ state, effects, update });
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
routeResultById({ state, effects, obj, result, rawLine });
|
|
918
|
+
} });
|
|
919
|
+
stdout.on('data', (chunk) => parser.feed({ chunk: chunk }));
|
|
886
920
|
child.stderr?.setEncoding('utf8');
|
|
887
921
|
child.stderr?.on('data', (chunk) => {
|
|
888
922
|
if (!modelUnavailableErrorCode || finished)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* dead, unreachable code would force either a contrived test or a
|
|
15
15
|
* coverage-suppression comment to satisfy this package's >=99% bar, and this
|
|
16
16
|
* repo's coverage-discipline convention calls for refactoring dead branches
|
|
17
|
-
* away rather than either of those. See
|
|
17
|
+
* away rather than either of those. See archived provenance ledger.
|
|
18
18
|
*/
|
|
19
19
|
/** A single streamed text delta event, as consumed by `emitWithTextSuppressor`. */
|
|
20
20
|
type EventSink = (event: {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* dead, unreachable code would force either a contrived test or a
|
|
15
15
|
* coverage-suppression comment to satisfy this package's >=99% bar, and this
|
|
16
16
|
* repo's coverage-discipline convention calls for refactoring dead branches
|
|
17
|
-
* away rather than either of those. See
|
|
17
|
+
* away rather than either of those. See archived provenance ledger.
|
|
18
18
|
*/
|
|
19
19
|
const ARTIFACT_OPEN_RE = /(?:<\s*\|?\s*DSML[\s,]+artifact\b[^>]*>|<\s*artifact\b[^>]*>)/i;
|
|
20
20
|
const DSML_ARTIFACT_CLOSE_RE = /(?:<\/artifact>|<\/\s*\|?\s*DSML\s*>|<\s*\|?\s*\/\s*DSML\s*\|?\s*>)/i;
|
|
@@ -8,7 +8,8 @@ import type { ChildProcess } from 'node:child_process';
|
|
|
8
8
|
/** A JSON-RPC 2.0 request or response identifier. May be a numeric integer or a string. */
|
|
9
9
|
export type JsonRpcId = string | number;
|
|
10
10
|
/** An arbitrary JSON object with string keys and unknown values. Used as a loose type for parsed RPC frames. */
|
|
11
|
-
|
|
11
|
+
/** ACP records may contain unvalidated provider values; this does not assert JSON safety. */
|
|
12
|
+
export type UnknownRecord = Record<string, unknown>;
|
|
12
13
|
/** Minimal writable interface required to send JSON-RPC newline-delimited frames to an ACP agent's stdin. */
|
|
13
14
|
export type RpcWritable = Pick<Writable, 'write' | 'end'>;
|
|
14
15
|
/** The Node `ChildProcess` handle for a spawned ACP agent subprocess. */
|
|
@@ -21,7 +22,7 @@ export type TimerHandle = ReturnType<typeof setTimeout>;
|
|
|
21
22
|
* (`'text_artifact'`). Inlined here rather than imported from an external
|
|
22
23
|
* contracts package — the upstream type is a trivial two-value literal union
|
|
23
24
|
* with no product-specific branding, so importing a whole package for it
|
|
24
|
-
* would be the tail wagging the dog. See
|
|
25
|
+
* would be the tail wagging the dog. See archived provenance ledger.
|
|
25
26
|
*/
|
|
26
27
|
export type ExecutionProfile = 'filesystem' | 'text_artifact';
|
|
27
28
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* diagnostics. Depends on acp/types, acp/json, and acp/account-failure;
|
|
6
6
|
* consumed exclusively by acp/session.ts.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { UnknownRecord } from './types.js';
|
|
9
9
|
import { type AccountFailureClassifier } from './account-failure.js';
|
|
10
10
|
/**
|
|
11
11
|
* Produces a shallow diagnostic snapshot of an ACP update object for the
|
|
@@ -17,7 +17,7 @@ import { type AccountFailureClassifier } from './account-failure.js';
|
|
|
17
17
|
* @param update - A parsed ACP `session/update` params object.
|
|
18
18
|
* @returns A flat diagnostic object suitable for inclusion in an agent event payload.
|
|
19
19
|
*/
|
|
20
|
-
export declare function acpRawEventShape(update:
|
|
20
|
+
export declare function acpRawEventShape(update: UnknownRecord): {
|
|
21
21
|
sessionUpdate: string | null;
|
|
22
22
|
keys: string[];
|
|
23
23
|
contentKind: string;
|
|
@@ -42,25 +42,25 @@ export declare function acpRawEventShape(update: JsonObject): {
|
|
|
42
42
|
*
|
|
43
43
|
* @param update - A parsed ACP `session/update` params object.
|
|
44
44
|
*/
|
|
45
|
-
export declare function acpUpdateStatus(update:
|
|
45
|
+
export declare function acpUpdateStatus(update: UnknownRecord): string;
|
|
46
46
|
/**
|
|
47
47
|
* Returns `true` when the update's `status` field represents a successful
|
|
48
48
|
* terminal state (`completed`, `complete`, `succeeded`, or `success`).
|
|
49
49
|
* Used to decide when to emit deferred artifact-write tool events.
|
|
50
50
|
*/
|
|
51
|
-
export declare function isAcpCompletedStatus(update:
|
|
51
|
+
export declare function isAcpCompletedStatus(update: UnknownRecord): boolean;
|
|
52
52
|
/**
|
|
53
53
|
* Returns `true` when the update's `status` field represents a terminal
|
|
54
54
|
* failure state (`failed`, `failure`, `error`, `cancelled`, or `canceled`).
|
|
55
55
|
* Used to clean up pending write-suppression state and emit failed tool events.
|
|
56
56
|
*/
|
|
57
|
-
export declare function isAcpTerminalFailureStatus(update:
|
|
57
|
+
export declare function isAcpTerminalFailureStatus(update: UnknownRecord): boolean;
|
|
58
58
|
/**
|
|
59
59
|
* Returns `true` when the update's status is `'retry'`. Signals that the AMR
|
|
60
60
|
* agent wants to restart the request; the session promoter maps this to a
|
|
61
61
|
* structured error payload via `promotedAmrRetryStatusPayload`.
|
|
62
62
|
*/
|
|
63
|
-
export declare function isAcpRetryStatus(update:
|
|
63
|
+
export declare function isAcpRetryStatus(update: UnknownRecord): boolean;
|
|
64
64
|
/**
|
|
65
65
|
* Recursively collects all text-bearing leaf values from an ACP update
|
|
66
66
|
* (strings, numbers, booleans) up to 4 levels deep. Used to produce a flat
|
|
@@ -83,7 +83,7 @@ export declare function acpUpdateDiagnosticText(value: unknown, depth?: number):
|
|
|
83
83
|
* `noopAccountFailureClassifier`, under which this always returns `null`.
|
|
84
84
|
* @returns A structured error payload with `message` and `error`, or `null`.
|
|
85
85
|
*/
|
|
86
|
-
export declare function promotedAmrRetryStatusPayload(update:
|
|
86
|
+
export declare function promotedAmrRetryStatusPayload(update: UnknownRecord, classifier?: AccountFailureClassifier): {
|
|
87
87
|
message: string;
|
|
88
88
|
error: {
|
|
89
89
|
code: string;
|
|
@@ -129,7 +129,7 @@ export declare function promotedAmrStderrPayload(chunk: string, classifier?: Acc
|
|
|
129
129
|
*
|
|
130
130
|
* @param update - A parsed ACP `session/update` params object.
|
|
131
131
|
*/
|
|
132
|
-
export declare function acpToolCallId(update:
|
|
132
|
+
export declare function acpToolCallId(update: UnknownRecord): string | null;
|
|
133
133
|
/**
|
|
134
134
|
* Returns `true` when the update's `title` or `name` field contains a word
|
|
135
135
|
* that indicates a file-write operation (`edit`, `write`, `create`, `update`,
|
|
@@ -138,7 +138,7 @@ export declare function acpToolCallId(update: JsonObject): string | null;
|
|
|
138
138
|
*
|
|
139
139
|
* @param update - A parsed ACP `session/update` params object.
|
|
140
140
|
*/
|
|
141
|
-
export declare function isAcpArtifactWriteLabel(update:
|
|
141
|
+
export declare function isAcpArtifactWriteLabel(update: UnknownRecord): boolean;
|
|
142
142
|
/**
|
|
143
143
|
* Returns `true` when an ACP update represents the terminal completion of an
|
|
144
144
|
* artifact-write tool call and should arm the DSML text suppressor. Requires
|
|
@@ -148,7 +148,7 @@ export declare function isAcpArtifactWriteLabel(update: JsonObject): boolean;
|
|
|
148
148
|
* @param update - A parsed ACP `session/update` params object.
|
|
149
149
|
* @param writeToolCallIds - The set of tool call ids identified as artifact writes so far.
|
|
150
150
|
*/
|
|
151
|
-
export declare function isAcpArtifactWriteUpdate(update:
|
|
151
|
+
export declare function isAcpArtifactWriteUpdate(update: UnknownRecord, writeToolCallIds: Set<string>): boolean;
|
|
152
152
|
/**
|
|
153
153
|
* Best-effort extraction of a concrete file path from an ACP artifact-write
|
|
154
154
|
* tool call update, checking three sources in priority order:
|
|
@@ -162,5 +162,5 @@ export declare function isAcpArtifactWriteUpdate(update: JsonObject, writeToolCa
|
|
|
162
162
|
* @param update - A parsed ACP `session/update` params object.
|
|
163
163
|
* @returns An absolute or relative file path string, or `null` when absent.
|
|
164
164
|
*/
|
|
165
|
-
export declare function acpArtifactWritePath(update:
|
|
165
|
+
export declare function acpArtifactWritePath(update: UnknownRecord): string | null;
|
|
166
166
|
//# sourceMappingURL=updates.d.ts.map
|
|
@@ -130,7 +130,7 @@ export function promotedAmrRetryStatusPayload(update, classifier = noopAccountFa
|
|
|
130
130
|
if (!isAcpRetryStatus(update))
|
|
131
131
|
return null;
|
|
132
132
|
const diagnosticText = acpUpdateDiagnosticText(update).join('\n');
|
|
133
|
-
const failure = classifier.classify(diagnosticText);
|
|
133
|
+
const failure = classifier.classify({ text: diagnosticText });
|
|
134
134
|
if (!failure)
|
|
135
135
|
return null;
|
|
136
136
|
return {
|
|
@@ -162,7 +162,7 @@ export function promotedAmrStderrPayload(chunk, classifier = noopAccountFailureC
|
|
|
162
162
|
return null;
|
|
163
163
|
if (!/\bretry\b/i.test(chunk))
|
|
164
164
|
return null;
|
|
165
|
-
const failure = classifier.classify(chunk);
|
|
165
|
+
const failure = classifier.classify({ text: chunk });
|
|
166
166
|
if (!failure)
|
|
167
167
|
return null;
|
|
168
168
|
return {
|