mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-10 19:32:21 +00:00
Compare commits
1 Commits
main
...
refactor/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dba368bc4 |
@@ -32,8 +32,8 @@ import {
|
||||
ClaudeAcpAgent,
|
||||
computeSessionFingerprint,
|
||||
runPromptWithCancellation,
|
||||
type SteerRequest,
|
||||
} from "../../v1/acp-agent.js";
|
||||
import type { SteerRequest } from "../../v1/steering.js";
|
||||
import { buildConfigOptions, discoverCustomAgents } from "../../session-config-options.js";
|
||||
import { createFastModeConfigOption } from "../../session-fast-mode.js";
|
||||
import {
|
||||
|
||||
@@ -147,6 +147,20 @@ import {
|
||||
clientSupportsAsyncTasks,
|
||||
} from "../async-tasks.js";
|
||||
import { SessionBackgroundWork } from "./background-work.js";
|
||||
import {
|
||||
ASYNC_TASK_STOP_METHOD,
|
||||
parseAsyncTaskStopRequest,
|
||||
type AsyncTaskStopRequest,
|
||||
type AsyncTaskStopResponse,
|
||||
} from "./async-task-stop.js";
|
||||
import {
|
||||
STEER_METHOD,
|
||||
STEER_PRIORITY_LATER,
|
||||
STEER_PRIORITY_NOW,
|
||||
parseSteerRequest,
|
||||
type SteerRequest,
|
||||
type SteerResponse,
|
||||
} from "./steering.js";
|
||||
import {
|
||||
AUTH_STATUS_UPDATE_METHOD,
|
||||
type AuthStatus,
|
||||
@@ -562,112 +576,6 @@ const TURN_NO_RESULT_MESSAGE =
|
||||
"The turn ended without a result: the agent went idle while this prompt was still in flight " +
|
||||
"(e.g. the model stream dropped mid-turn). Any partial output may be incomplete; please retry.";
|
||||
|
||||
/** Custom (extension) request method a client uses to steer the turn that is
|
||||
* currently running: the message is injected into the in-flight turn rather
|
||||
* than queued as a separate `session/prompt`. Named `_session/steering` per the
|
||||
* agreed ACP steering wire protocol; advertised to clients via the top-level
|
||||
* `InitializeResponse._meta.steering.supported`. */
|
||||
const STEER_METHOD = "_session/steering";
|
||||
|
||||
/** Stops one Claude background task without cancelling the parent prompt turn. */
|
||||
const ASYNC_TASK_STOP_METHOD = "_session/async_task/stop";
|
||||
|
||||
type AsyncTaskStopRequest = {
|
||||
sessionId: string;
|
||||
asyncTaskId: string;
|
||||
};
|
||||
|
||||
type AsyncTaskStopResponse = {
|
||||
stopped: boolean;
|
||||
};
|
||||
|
||||
function parseAsyncTaskStopRequest(value: unknown): AsyncTaskStopRequest {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw RequestError.invalidParams(undefined, "async task stop params must be an object");
|
||||
}
|
||||
const params = value as Record<string, unknown>;
|
||||
const sessionId = typeof params.sessionId === "string" ? params.sessionId.trim() : "";
|
||||
const asyncTaskId = typeof params.asyncTaskId === "string" ? params.asyncTaskId.trim() : "";
|
||||
if (!sessionId) {
|
||||
throw RequestError.invalidParams(
|
||||
undefined,
|
||||
"async task stop params require a non-empty sessionId",
|
||||
);
|
||||
}
|
||||
if (!asyncTaskId) {
|
||||
throw RequestError.invalidParams(
|
||||
undefined,
|
||||
"async task stop params require a non-empty asyncTaskId",
|
||||
);
|
||||
}
|
||||
return { sessionId, asyncTaskId };
|
||||
}
|
||||
|
||||
/** How urgently the SDK delivers a steered message relative to the running
|
||||
* turn — an internal Claude implementation detail, not part of the wire
|
||||
* contract. `now` pre-empts the current generation, while `later` waits for a
|
||||
* pending permission/elicitation callback to settle instead of cancelling its
|
||||
* ACP request and hiding the client's user-input card (IJAI-1191). */
|
||||
const STEER_PRIORITY_NOW = "now" as const;
|
||||
const STEER_PRIORITY_LATER = "later" as const;
|
||||
|
||||
/** Request-level steering options. `promptRequired` is opt-in so existing Hosts
|
||||
* keep the established idle fallback behavior. */
|
||||
type SteerMeta = {
|
||||
[key: string]: unknown;
|
||||
steering?: {
|
||||
idleBehavior?: "promptRequired";
|
||||
};
|
||||
};
|
||||
|
||||
/** Params of a {@link STEER_METHOD} request. Shaped like the relevant subset of
|
||||
* a `PromptRequest` so the same `promptToClaude` conversion applies. Delivery
|
||||
* priority is deliberately NOT exposed here — it's an internal detail the agent
|
||||
* chooses (see {@link STEER_PRIORITY}). */
|
||||
export type SteerRequest = {
|
||||
sessionId: string;
|
||||
prompt: PromptRequest["prompt"];
|
||||
_meta?: SteerMeta | null;
|
||||
};
|
||||
|
||||
/** Result of a {@link STEER_METHOD} request. The legacy `startedNewTurn` result
|
||||
* remains the default idle behavior; `promptRequired` is returned only when the
|
||||
* Host explicitly opts into the host-owned fallback in request `_meta`. */
|
||||
export type SteerResponse =
|
||||
| { outcome: "injected" }
|
||||
| { outcome: "startedNewTurn" }
|
||||
| { outcome: "promptRequired"; reason: "noRunningTurn" };
|
||||
|
||||
/** Validate raw JSON-RPC params into a {@link SteerRequest}. Kept minimal — the
|
||||
* content blocks are handed to `promptToClaude`, which tolerates unknown block
|
||||
* types — but `sessionId` and a non-empty `prompt` array are required. */
|
||||
function parseSteerRequest(params: unknown): SteerRequest {
|
||||
if (!params || typeof params !== "object") {
|
||||
throw RequestError.invalidParams(undefined, "steer params must be an object");
|
||||
}
|
||||
const { sessionId, prompt, _meta } = params as Record<string, unknown>;
|
||||
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
||||
throw RequestError.invalidParams(undefined, "steer params require a non-empty sessionId");
|
||||
}
|
||||
if (!Array.isArray(prompt) || prompt.length === 0) {
|
||||
throw RequestError.invalidParams(undefined, "steer params require a non-empty prompt array");
|
||||
}
|
||||
const steering =
|
||||
_meta && typeof _meta === "object" ? (_meta as Record<string, unknown>).steering : undefined;
|
||||
const idleBehavior =
|
||||
steering && typeof steering === "object"
|
||||
? (steering as Record<string, unknown>).idleBehavior
|
||||
: undefined;
|
||||
if (idleBehavior !== undefined && idleBehavior !== "promptRequired") {
|
||||
throw RequestError.invalidParams(undefined, "unsupported steering idleBehavior");
|
||||
}
|
||||
return {
|
||||
sessionId,
|
||||
prompt: prompt as PromptRequest["prompt"],
|
||||
_meta: _meta as SteerMeta | null | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** Who started a turn. `idleCompaction` is the adapter's own `/compact` (see
|
||||
* `IdleCompaction`): it is not a user prompt, so it neither disarms idle
|
||||
* compaction, asks for a title from its prompt, nor clears the published prompt suggestion. */
|
||||
|
||||
35
src/v1/async-task-stop.ts
Normal file
35
src/v1/async-task-stop.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
/** Stops one Claude background task without cancelling the parent prompt turn. */
|
||||
export const ASYNC_TASK_STOP_METHOD = "_session/async_task/stop";
|
||||
|
||||
export type AsyncTaskStopRequest = {
|
||||
sessionId: string;
|
||||
asyncTaskId: string;
|
||||
};
|
||||
|
||||
export type AsyncTaskStopResponse = {
|
||||
stopped: boolean;
|
||||
};
|
||||
|
||||
export function parseAsyncTaskStopRequest(value: unknown): AsyncTaskStopRequest {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw RequestError.invalidParams(undefined, "async task stop params must be an object");
|
||||
}
|
||||
const params = value as Record<string, unknown>;
|
||||
const sessionId = typeof params.sessionId === "string" ? params.sessionId.trim() : "";
|
||||
const asyncTaskId = typeof params.asyncTaskId === "string" ? params.asyncTaskId.trim() : "";
|
||||
if (!sessionId) {
|
||||
throw RequestError.invalidParams(
|
||||
undefined,
|
||||
"async task stop params require a non-empty sessionId",
|
||||
);
|
||||
}
|
||||
if (!asyncTaskId) {
|
||||
throw RequestError.invalidParams(
|
||||
undefined,
|
||||
"async task stop params require a non-empty asyncTaskId",
|
||||
);
|
||||
}
|
||||
return { sessionId, asyncTaskId };
|
||||
}
|
||||
73
src/v1/steering.ts
Normal file
73
src/v1/steering.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { PromptRequest, RequestError } from "@agentclientprotocol/sdk/unstable";
|
||||
|
||||
/** Custom (extension) request method a client uses to steer the turn that is
|
||||
* currently running: the message is injected into the in-flight turn rather
|
||||
* than queued as a separate `session/prompt`. Named `_session/steering` per the
|
||||
* agreed ACP steering wire protocol; advertised to clients via the top-level
|
||||
* `InitializeResponse._meta.steering.supported`. */
|
||||
export const STEER_METHOD = "_session/steering";
|
||||
|
||||
/** How urgently the SDK delivers a steered message relative to the running
|
||||
* turn — an internal Claude implementation detail, not part of the wire
|
||||
* contract. `now` pre-empts the current generation, while `later` waits for a
|
||||
* pending permission/elicitation callback to settle instead of cancelling its
|
||||
* ACP request and hiding the client's user-input card (IJAI-1191). */
|
||||
export const STEER_PRIORITY_NOW = "now" as const;
|
||||
export const STEER_PRIORITY_LATER = "later" as const;
|
||||
|
||||
/** Request-level steering options. `promptRequired` is opt-in so existing Hosts
|
||||
* keep the established idle fallback behavior. */
|
||||
type SteerMeta = {
|
||||
[key: string]: unknown;
|
||||
steering?: {
|
||||
idleBehavior?: "promptRequired";
|
||||
};
|
||||
};
|
||||
|
||||
/** Params of a {@link STEER_METHOD} request. Shaped like the relevant subset of
|
||||
* a `PromptRequest` so the same `promptToClaude` conversion applies. Delivery
|
||||
* priority is deliberately NOT exposed here — it's an internal detail the agent
|
||||
* chooses (see {@link STEER_PRIORITY}). */
|
||||
export type SteerRequest = {
|
||||
sessionId: string;
|
||||
prompt: PromptRequest["prompt"];
|
||||
_meta?: SteerMeta | null;
|
||||
};
|
||||
|
||||
/** Result of a {@link STEER_METHOD} request. The legacy `startedNewTurn` result
|
||||
* remains the default idle behavior; `promptRequired` is returned only when the
|
||||
* Host explicitly opts into the host-owned fallback in request `_meta`. */
|
||||
export type SteerResponse =
|
||||
| { outcome: "injected" }
|
||||
| { outcome: "startedNewTurn" }
|
||||
| { outcome: "promptRequired"; reason: "noRunningTurn" };
|
||||
|
||||
/** Validate raw JSON-RPC params into a {@link SteerRequest}. Kept minimal — the
|
||||
* content blocks are handed to `promptToClaude`, which tolerates unknown block
|
||||
* types — but `sessionId` and a non-empty `prompt` array are required. */
|
||||
export function parseSteerRequest(params: unknown): SteerRequest {
|
||||
if (!params || typeof params !== "object") {
|
||||
throw RequestError.invalidParams(undefined, "steer params must be an object");
|
||||
}
|
||||
const { sessionId, prompt, _meta } = params as Record<string, unknown>;
|
||||
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
||||
throw RequestError.invalidParams(undefined, "steer params require a non-empty sessionId");
|
||||
}
|
||||
if (!Array.isArray(prompt) || prompt.length === 0) {
|
||||
throw RequestError.invalidParams(undefined, "steer params require a non-empty prompt array");
|
||||
}
|
||||
const steering =
|
||||
_meta && typeof _meta === "object" ? (_meta as Record<string, unknown>).steering : undefined;
|
||||
const idleBehavior =
|
||||
steering && typeof steering === "object"
|
||||
? (steering as Record<string, unknown>).idleBehavior
|
||||
: undefined;
|
||||
if (idleBehavior !== undefined && idleBehavior !== "promptRequired") {
|
||||
throw RequestError.invalidParams(undefined, "unsupported steering idleBehavior");
|
||||
}
|
||||
return {
|
||||
sessionId,
|
||||
prompt: prompt as PromptRequest["prompt"],
|
||||
_meta: _meta as SteerMeta | null | undefined,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user