@tanstack/ai-acp 0.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.
Files changed (54) hide show
  1. package/README.md +428 -0
  2. package/dist/esm/adapters/compatible.d.ts +206 -0
  3. package/dist/esm/adapters/compatible.js +313 -0
  4. package/dist/esm/adapters/compatible.js.map +1 -0
  5. package/dist/esm/adapters/projection.d.ts +29 -0
  6. package/dist/esm/adapters/projection.js +83 -0
  7. package/dist/esm/adapters/projection.js.map +1 -0
  8. package/dist/esm/index.d.ts +21 -0
  9. package/dist/esm/index.js +35 -0
  10. package/dist/esm/index.js.map +1 -0
  11. package/dist/esm/messages/prompt.d.ts +19 -0
  12. package/dist/esm/messages/prompt.js +37 -0
  13. package/dist/esm/messages/prompt.js.map +1 -0
  14. package/dist/esm/permissions.d.ts +8 -0
  15. package/dist/esm/permissions.js +44 -0
  16. package/dist/esm/permissions.js.map +1 -0
  17. package/dist/esm/session/acp-client.d.ts +37 -0
  18. package/dist/esm/session/acp-client.js +140 -0
  19. package/dist/esm/session/acp-client.js.map +1 -0
  20. package/dist/esm/session/sandbox-server.d.ts +46 -0
  21. package/dist/esm/session/sandbox-server.js +114 -0
  22. package/dist/esm/session/sandbox-server.js.map +1 -0
  23. package/dist/esm/stream/queue.d.ts +15 -0
  24. package/dist/esm/stream/queue.js +54 -0
  25. package/dist/esm/stream/queue.js.map +1 -0
  26. package/dist/esm/stream/translate.d.ts +38 -0
  27. package/dist/esm/stream/translate.js +320 -0
  28. package/dist/esm/stream/translate.js.map +1 -0
  29. package/dist/esm/transport/resolve.d.ts +7 -0
  30. package/dist/esm/transport/resolve.js +34 -0
  31. package/dist/esm/transport/resolve.js.map +1 -0
  32. package/dist/esm/transport/stdio.d.ts +3 -0
  33. package/dist/esm/transport/stdio.js +51 -0
  34. package/dist/esm/transport/stdio.js.map +1 -0
  35. package/dist/esm/transport/types.d.ts +29 -0
  36. package/dist/esm/transport/websocket.d.ts +17 -0
  37. package/dist/esm/transport/websocket.js +135 -0
  38. package/dist/esm/transport/websocket.js.map +1 -0
  39. package/dist/esm/types/acp-types.d.ts +75 -0
  40. package/package.json +61 -0
  41. package/src/adapters/compatible.ts +675 -0
  42. package/src/adapters/projection.ts +164 -0
  43. package/src/index.ts +85 -0
  44. package/src/messages/prompt.ts +71 -0
  45. package/src/permissions.ts +71 -0
  46. package/src/session/acp-client.ts +238 -0
  47. package/src/session/sandbox-server.ts +195 -0
  48. package/src/stream/queue.ts +61 -0
  49. package/src/stream/translate.ts +412 -0
  50. package/src/transport/resolve.ts +46 -0
  51. package/src/transport/stdio.ts +63 -0
  52. package/src/transport/types.ts +33 -0
  53. package/src/transport/websocket.ts +192 -0
  54. package/src/types/acp-types.ts +79 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sources":["../../../src/transport/websocket.ts"],"sourcesContent":["import { ndJsonStream } from '@agentclientprotocol/sdk'\nimport type { AcpJsonRpcStream, AcpMessageFraming } from './types'\n\nfunction waitForWebSocketOpen(\n ws: WebSocket,\n signal?: AbortSignal,\n): Promise<void> {\n if (ws.readyState === WebSocket.OPEN) return Promise.resolve()\n return new Promise((resolve, reject) => {\n const onOpen = (): void => {\n cleanup()\n resolve()\n }\n const onError = (): void => {\n cleanup()\n reject(new Error('WebSocket connection failed'))\n }\n const onAbort = (): void => {\n cleanup()\n ws.close()\n reject(signal?.reason ?? new Error('WebSocket connection aborted'))\n }\n const cleanup = (): void => {\n ws.removeEventListener('open', onOpen)\n ws.removeEventListener('error', onError)\n signal?.removeEventListener('abort', onAbort)\n }\n ws.addEventListener('open', onOpen)\n ws.addEventListener('error', onError)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n/**\n * Wrap a stream controller so its terminal calls are idempotent. A WebSocket can\n * fire `close` after `error` (or after the reader cancels), and closing/erroring\n * an already-settled controller throws `ERR_INVALID_STATE` out of the listener.\n */\nfunction idempotentController<T>(\n controller: ReadableStreamDefaultController<T>,\n): {\n enqueue: (chunk: T) => void\n close: () => void\n error: (reason: unknown) => void\n} {\n let settled = false\n return {\n enqueue: (chunk) => {\n if (!settled) controller.enqueue(chunk)\n },\n close: () => {\n if (settled) return\n settled = true\n controller.close()\n },\n error: (reason) => {\n if (settled) return\n settled = true\n controller.error(reason)\n },\n }\n}\n\n/**\n * One JSON-RPC object per WebSocket text frame (e.g. `grok agent serve`).\n */\nexport function webSocketFrameToAcpStream(ws: WebSocket): AcpJsonRpcStream {\n const decoder = new TextDecoder()\n\n const readable = new ReadableStream<unknown>({\n start(rawController) {\n const controller = idempotentController(rawController)\n ws.addEventListener('message', (event) => {\n const text =\n typeof event.data === 'string'\n ? event.data\n : decoder.decode(event.data as ArrayBuffer)\n const trimmed = text.trim()\n if (trimmed === '') return\n try {\n controller.enqueue(JSON.parse(trimmed))\n } catch (error) {\n controller.error(\n error instanceof Error ? error : new Error(String(error)),\n )\n }\n })\n ws.addEventListener('close', () => controller.close())\n ws.addEventListener('error', () =>\n controller.error(new Error('WebSocket connection error')),\n )\n },\n cancel() {\n ws.close()\n },\n })\n\n const writable = new WritableStream({\n write(message) {\n ws.send(JSON.stringify(message))\n },\n close() {\n ws.close()\n },\n })\n\n return { readable, writable } as AcpJsonRpcStream\n}\n\nfunction webSocketNdjsonToAcpStream(ws: WebSocket): AcpJsonRpcStream {\n const encoder = new TextEncoder()\n const decoder = new TextDecoder()\n\n const readable = new ReadableStream<Uint8Array>({\n start(rawController) {\n const controller = idempotentController(rawController)\n ws.addEventListener('message', (event) => {\n const text =\n typeof event.data === 'string'\n ? event.data\n : decoder.decode(event.data as ArrayBuffer)\n controller.enqueue(encoder.encode(text))\n })\n ws.addEventListener('close', () => controller.close())\n ws.addEventListener('error', () =>\n controller.error(new Error('WebSocket connection error')),\n )\n },\n cancel() {\n ws.close()\n },\n })\n\n const writable = new WritableStream<Uint8Array>({\n write(chunk) {\n ws.send(chunk)\n },\n close() {\n ws.close()\n },\n })\n\n return ndJsonStream(writable, readable)\n}\n\nexport interface ConnectAcpWebSocketOptions {\n headers?: Record<string, string>\n signal?: AbortSignal\n framing?: AcpMessageFraming\n}\n\nexport interface AcpWebSocketConnection {\n stream: AcpJsonRpcStream\n close: () => void\n}\n\n/**\n * Open a WebSocket to an in-sandbox ACP server and adapt it for\n * {@link ClientSideConnection}.\n */\nfunction openWebSocket(\n url: string,\n headers?: Record<string, string>,\n): WebSocket {\n if (headers === undefined) return new WebSocket(url)\n // Node/ws accepts `{ headers }`; DOM lib constructor types omit this overload.\n return Reflect.construct(WebSocket, [url, { headers }]) as WebSocket\n}\n\nexport async function connectAcpWebSocket(\n url: string,\n options: ConnectAcpWebSocketOptions = {},\n): Promise<AcpWebSocketConnection> {\n const ws = openWebSocket(url, options.headers)\n await waitForWebSocketOpen(ws, options.signal)\n\n const framing = options.framing ?? 'frame'\n const stream =\n framing === 'ndjson'\n ? webSocketNdjsonToAcpStream(ws)\n : webSocketFrameToAcpStream(ws)\n\n return {\n stream,\n close: () => ws.close(),\n }\n}\n\n/** Convert an HTTP sandbox channel URL to a WebSocket base URL. */\nexport function httpChannelUrlToWsBase(channelUrl: string): string {\n return channelUrl.replace(/^http/i, 'ws').replace(/\\/$/, '')\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Structural subset of the Agent Client Protocol (ACP) types that harness
3
+ * adapters consume.
4
+ *
5
+ * Defined structurally (rather than imported from `@agentclientprotocol/sdk`)
6
+ * so the stream translator stays a pure, fixture-testable state machine.
7
+ */
8
+ export type AcpContentBlock = {
9
+ type: 'text';
10
+ text: string;
11
+ } | {
12
+ type: string;
13
+ [key: string]: unknown;
14
+ };
15
+ export type AcpToolCallStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
16
+ export interface AcpToolCallUpdate {
17
+ toolCallId: string;
18
+ title?: string | null;
19
+ kind?: string | null;
20
+ status?: AcpToolCallStatus | null;
21
+ rawInput?: unknown;
22
+ rawOutput?: unknown;
23
+ content?: Array<{
24
+ type: string;
25
+ content?: AcpContentBlock;
26
+ [key: string]: unknown;
27
+ }> | null;
28
+ }
29
+ export type AcpSessionUpdate = {
30
+ sessionUpdate: 'agent_message_chunk';
31
+ content: AcpContentBlock;
32
+ } | {
33
+ sessionUpdate: 'agent_thought_chunk';
34
+ content: AcpContentBlock;
35
+ } | ({
36
+ sessionUpdate: 'tool_call';
37
+ } & AcpToolCallUpdate) | ({
38
+ sessionUpdate: 'tool_call_update';
39
+ } & AcpToolCallUpdate) | {
40
+ sessionUpdate: 'plan';
41
+ entries: Array<unknown>;
42
+ } | {
43
+ sessionUpdate: 'available_commands_update';
44
+ } | {
45
+ sessionUpdate: 'current_mode_update';
46
+ } | {
47
+ sessionUpdate: 'user_message_chunk';
48
+ content: AcpContentBlock;
49
+ };
50
+ export type AcpStopReason = 'end_turn' | 'max_tokens' | 'max_turn_requests' | 'refusal' | 'cancelled' | (string & {});
51
+ export interface AcpUsage {
52
+ inputTokens?: number | null;
53
+ outputTokens?: number | null;
54
+ totalTokens?: number | null;
55
+ cachedReadTokens?: number | null;
56
+ thoughtTokens?: number | null;
57
+ }
58
+ export interface AcpPermissionOption {
59
+ optionId: string;
60
+ name: string;
61
+ kind: 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
62
+ }
63
+ export interface AcpPermissionRequest {
64
+ sessionId: string;
65
+ toolCall: AcpToolCallUpdate;
66
+ options: Array<AcpPermissionOption>;
67
+ }
68
+ export type AcpPermissionOutcome = {
69
+ outcome: 'cancelled';
70
+ } | {
71
+ outcome: 'selected';
72
+ optionId: string;
73
+ };
74
+ export type AcpPermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions';
75
+ export type PermissionHandler = (request: AcpPermissionRequest) => Promise<AcpPermissionOutcome> | AcpPermissionOutcome;
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@tanstack/ai-acp",
3
+ "version": "0.1.0",
4
+ "description": "Shared Agent Client Protocol (ACP) transport, session, and AG-UI translation for TanStack AI harness adapters.",
5
+ "author": "",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/ai.git",
10
+ "directory": "packages/ai-acp"
11
+ },
12
+ "keywords": [
13
+ "ai",
14
+ "ai-sdk",
15
+ "typescript",
16
+ "tanstack",
17
+ "acp",
18
+ "agent-client-protocol",
19
+ "harness",
20
+ "sandbox"
21
+ ],
22
+ "type": "module",
23
+ "module": "./dist/esm/index.js",
24
+ "types": "./dist/esm/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/esm/index.d.ts",
28
+ "import": "./dist/esm/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "src"
34
+ ],
35
+ "scripts": {
36
+ "build": "vite build",
37
+ "clean": "premove ./build ./dist",
38
+ "lint:fix": "eslint ./src --fix",
39
+ "test:build": "publint --strict",
40
+ "test:eslint": "eslint ./src",
41
+ "test:lib": "vitest",
42
+ "test:lib:dev": "pnpm test:lib --watch",
43
+ "test:types": "tsc"
44
+ },
45
+ "dependencies": {
46
+ "@agentclientprotocol/sdk": "^0.25.0"
47
+ },
48
+ "peerDependencies": {
49
+ "@tanstack/ai": "workspace:^",
50
+ "@tanstack/ai-sandbox": "workspace:^"
51
+ },
52
+ "devDependencies": {
53
+ "@tanstack/ai": "workspace:*",
54
+ "@tanstack/ai-sandbox": "workspace:*",
55
+ "@tanstack/ai-sandbox-local-process": "workspace:*",
56
+ "@vitest/coverage-v8": "4.0.14"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ }
61
+ }