@tanstack/ai-acp 0.2.2 → 0.2.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.js","sources":["../../src/permissions.ts"],"sourcesContent":["import { approvalId } from '@tanstack/ai-sandbox'\nimport { matchBridgedToolName } from './stream/translate'\nimport type {\n AcpPermissionMode,\n AcpPermissionOutcome,\n AcpPermissionRequest,\n PermissionHandler,\n} from './types/acp-types'\n\nexport type { AcpPermissionMode, PermissionHandler }\n\nconst EDIT_KINDS = new Set(['edit', 'move', 'delete'])\n\nfunction pickOption(\n request: AcpPermissionRequest,\n kinds: Array<string>,\n): AcpPermissionOutcome {\n for (const kind of kinds) {\n const option = request.options.find((candidate) => candidate.kind === kind)\n if (option) return { outcome: 'selected', optionId: option.optionId }\n }\n return { outcome: 'cancelled' }\n}\n\nexport function resolvePermission(\n request: AcpPermissionRequest,\n mode: AcpPermissionMode,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): AcpPermissionOutcome {\n const allow = () => pickOption(request, ['allow_once', 'allow_always'])\n const reject = () => pickOption(request, ['reject_once', 'reject_always'])\n\n if (\n matchBridgedToolName(request.toolCall.title, bridgedToolNames) !== undefined\n ) {\n return allow()\n }\n if (mode === 'bypassPermissions') return allow()\n if (mode === 'acceptEdits' && EDIT_KINDS.has(request.toolCall.kind ?? '')) {\n return allow()\n }\n return reject()\n}\n\nexport function resolveInteractivePermission(\n request: AcpPermissionRequest,\n mode: AcpPermissionMode,\n bridgedToolNames: ReadonlySet<string> | undefined,\n approvals: ReadonlyMap<string, boolean> | undefined,\n provider: string,\n): { outcome: AcpPermissionOutcome; approvalId?: string; title?: string } {\n const allow = (): AcpPermissionOutcome =>\n pickOption(request, ['allow_once', 'allow_always'])\n const reject = (): AcpPermissionOutcome =>\n pickOption(request, ['reject_once', 'reject_always'])\n const title = request.toolCall.title ?? request.toolCall.toolCallId\n\n if (matchBridgedToolName(title, bridgedToolNames) !== undefined) {\n return { outcome: allow() }\n }\n if (mode === 'bypassPermissions') return { outcome: allow() }\n if (mode === 'acceptEdits' && EDIT_KINDS.has(request.toolCall.kind ?? '')) {\n return { outcome: allow() }\n }\n\n const id = approvalId({ provider, kind: 'tool', target: title })\n const granted = approvals?.get(id)\n if (granted === true) return { outcome: allow() }\n if (granted === false) return { outcome: reject() }\n return { outcome: reject(), approvalId: id, title }\n}\n"],"names":[],"mappings":";;AAWA,MAAM,aAAa,oBAAI,IAAI,CAAC,QAAQ,QAAQ,QAAQ,CAAC;AAErD,SAAS,WACP,SACA,OACsB;AACtB,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,QAAQ,QAAQ,KAAK,CAAC,cAAc,UAAU,SAAS,IAAI;AAC1E,QAAI,OAAQ,QAAO,EAAE,SAAS,YAAY,UAAU,OAAO,SAAA;AAAA,EAC7D;AACA,SAAO,EAAE,SAAS,YAAA;AACpB;AAEO,SAAS,kBACd,SACA,MACA,kBACsB;AACtB,QAAM,QAAQ,MAAM,WAAW,SAAS,CAAC,cAAc,cAAc,CAAC;AACtE,QAAM,SAAS,MAAM,WAAW,SAAS,CAAC,eAAe,eAAe,CAAC;AAEzE,MACE,qBAAqB,QAAQ,SAAS,OAAO,gBAAgB,MAAM,QACnE;AACA,WAAO,MAAA;AAAA,EACT;AACA,MAAI,SAAS,oBAAqB,QAAO,MAAA;AACzC,MAAI,SAAS,iBAAiB,WAAW,IAAI,QAAQ,SAAS,QAAQ,EAAE,GAAG;AACzE,WAAO,MAAA;AAAA,EACT;AACA,SAAO,OAAA;AACT;AAEO,SAAS,6BACd,SACA,MACA,kBACA,WACA,UACwE;AACxE,QAAM,QAAQ,MACZ,WAAW,SAAS,CAAC,cAAc,cAAc,CAAC;AACpD,QAAM,SAAS,MACb,WAAW,SAAS,CAAC,eAAe,eAAe,CAAC;AACtD,QAAM,QAAQ,QAAQ,SAAS,SAAS,QAAQ,SAAS;AAEzD,MAAI,qBAAqB,OAAO,gBAAgB,MAAM,QAAW;AAC/D,WAAO,EAAE,SAAS,QAAM;AAAA,EAC1B;AACA,MAAI,SAAS,oBAAqB,QAAO,EAAE,SAAS,QAAM;AAC1D,MAAI,SAAS,iBAAiB,WAAW,IAAI,QAAQ,SAAS,QAAQ,EAAE,GAAG;AACzE,WAAO,EAAE,SAAS,QAAM;AAAA,EAC1B;AAEA,QAAM,KAAK,WAAW,EAAE,UAAU,MAAM,QAAQ,QAAQ,OAAO;AAC/D,QAAM,UAAU,WAAW,IAAI,EAAE;AACjC,MAAI,YAAY,KAAM,QAAO,EAAE,SAAS,QAAM;AAC9C,MAAI,YAAY,MAAO,QAAO,EAAE,SAAS,SAAO;AAChD,SAAO,EAAE,SAAS,OAAA,GAAU,YAAY,IAAI,MAAA;AAC9C;"}
1
+ {"version":3,"file":"permissions.js","names":[],"sources":["../../src/permissions.ts"],"sourcesContent":["import { approvalId } from '@tanstack/ai-sandbox'\nimport { matchBridgedToolName } from './stream/translate'\nimport type {\n AcpPermissionMode,\n AcpPermissionOutcome,\n AcpPermissionRequest,\n PermissionHandler,\n} from './types/acp-types'\n\nexport type { AcpPermissionMode, PermissionHandler }\n\nconst EDIT_KINDS = new Set(['edit', 'move', 'delete'])\n\nfunction pickOption(\n request: AcpPermissionRequest,\n kinds: Array<string>,\n): AcpPermissionOutcome {\n for (const kind of kinds) {\n const option = request.options.find((candidate) => candidate.kind === kind)\n if (option) return { outcome: 'selected', optionId: option.optionId }\n }\n return { outcome: 'cancelled' }\n}\n\nexport function resolvePermission(\n request: AcpPermissionRequest,\n mode: AcpPermissionMode,\n bridgedToolNames: ReadonlySet<string> | undefined,\n): AcpPermissionOutcome {\n const allow = () => pickOption(request, ['allow_once', 'allow_always'])\n const reject = () => pickOption(request, ['reject_once', 'reject_always'])\n\n if (\n matchBridgedToolName(request.toolCall.title, bridgedToolNames) !== undefined\n ) {\n return allow()\n }\n if (mode === 'bypassPermissions') return allow()\n if (mode === 'acceptEdits' && EDIT_KINDS.has(request.toolCall.kind ?? '')) {\n return allow()\n }\n return reject()\n}\n\nexport function resolveInteractivePermission(\n request: AcpPermissionRequest,\n mode: AcpPermissionMode,\n bridgedToolNames: ReadonlySet<string> | undefined,\n approvals: ReadonlyMap<string, boolean> | undefined,\n provider: string,\n): { outcome: AcpPermissionOutcome; approvalId?: string; title?: string } {\n const allow = (): AcpPermissionOutcome =>\n pickOption(request, ['allow_once', 'allow_always'])\n const reject = (): AcpPermissionOutcome =>\n pickOption(request, ['reject_once', 'reject_always'])\n const title = request.toolCall.title ?? request.toolCall.toolCallId\n\n if (matchBridgedToolName(title, bridgedToolNames) !== undefined) {\n return { outcome: allow() }\n }\n if (mode === 'bypassPermissions') return { outcome: allow() }\n if (mode === 'acceptEdits' && EDIT_KINDS.has(request.toolCall.kind ?? '')) {\n return { outcome: allow() }\n }\n\n const id = approvalId({ provider, kind: 'tool', target: title })\n const granted = approvals?.get(id)\n if (granted === true) return { outcome: allow() }\n if (granted === false) return { outcome: reject() }\n return { outcome: reject(), approvalId: id, title }\n}\n"],"mappings":";;;AAWA,IAAM,6BAAa,IAAI,IAAI;CAAC;CAAQ;CAAQ;AAAQ,CAAC;AAErD,SAAS,WACP,SACA,OACsB;CACtB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,QAAQ,QAAQ,MAAM,cAAc,UAAU,SAAS,IAAI;EAC1E,IAAI,QAAQ,OAAO;GAAE,SAAS;GAAY,UAAU,OAAO;EAAS;CACtE;CACA,OAAO,EAAE,SAAS,YAAY;AAChC;AAEA,SAAgB,kBACd,SACA,MACA,kBACsB;CACtB,MAAM,cAAc,WAAW,SAAS,CAAC,cAAc,cAAc,CAAC;CACtE,MAAM,eAAe,WAAW,SAAS,CAAC,eAAe,eAAe,CAAC;CAEzE,IACE,qBAAqB,QAAQ,SAAS,OAAO,gBAAgB,MAAM,KAAA,GAEnE,OAAO,MAAM;CAEf,IAAI,SAAS,qBAAqB,OAAO,MAAM;CAC/C,IAAI,SAAS,iBAAiB,WAAW,IAAI,QAAQ,SAAS,QAAQ,EAAE,GACtE,OAAO,MAAM;CAEf,OAAO,OAAO;AAChB;AAEA,SAAgB,6BACd,SACA,MACA,kBACA,WACA,UACwE;CACxE,MAAM,cACJ,WAAW,SAAS,CAAC,cAAc,cAAc,CAAC;CACpD,MAAM,eACJ,WAAW,SAAS,CAAC,eAAe,eAAe,CAAC;CACtD,MAAM,QAAQ,QAAQ,SAAS,SAAS,QAAQ,SAAS;CAEzD,IAAI,qBAAqB,OAAO,gBAAgB,MAAM,KAAA,GACpD,OAAO,EAAE,SAAS,MAAM,EAAE;CAE5B,IAAI,SAAS,qBAAqB,OAAO,EAAE,SAAS,MAAM,EAAE;CAC5D,IAAI,SAAS,iBAAiB,WAAW,IAAI,QAAQ,SAAS,QAAQ,EAAE,GACtE,OAAO,EAAE,SAAS,MAAM,EAAE;CAG5B,MAAM,KAAK,WAAW;EAAE;EAAU,MAAM;EAAQ,QAAQ;CAAM,CAAC;CAC/D,MAAM,UAAU,WAAW,IAAI,EAAE;CACjC,IAAI,YAAY,MAAM,OAAO,EAAE,SAAS,MAAM,EAAE;CAChD,IAAI,YAAY,OAAO,OAAO,EAAE,SAAS,OAAO,EAAE;CAClD,OAAO;EAAE,SAAS,OAAO;EAAG,YAAY;EAAI;CAAM;AACpD"}
@@ -1,140 +1,124 @@
1
- import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream } from "@agentclientprotocol/sdk";
2
1
  import { spawnHandleToAcpTransport } from "../transport/stdio.js";
3
- const CLIENT_INFO = {
4
- name: "@tanstack/ai-acp",
5
- title: "TanStack AI",
6
- version: "0.1.0"
2
+ import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream } from "@agentclientprotocol/sdk";
3
+ //#region src/session/acp-client.ts
4
+ /**
5
+ * Identifies this client to the agent in the ACP `initialize` handshake. The
6
+ * `version` is informational (sent in `clientInfo`); keep it loosely in step
7
+ * with the package version. `clientInfo` is currently optional in the protocol
8
+ * but will become required, so we always send it.
9
+ * ponytail: hardcoded — clientInfo is diagnostic; a stale patch version is harmless.
10
+ */
11
+ var CLIENT_INFO = {
12
+ name: "@tanstack/ai-acp",
13
+ title: "TanStack AI",
14
+ version: "0.1.0"
7
15
  };
8
16
  function streamFromTransport(transport) {
9
- if (transport.kind === "stdio") {
10
- const byteTransport = spawnHandleToAcpTransport(transport.process);
11
- return {
12
- stream: ndJsonStream(byteTransport.writable, byteTransport.readable),
13
- teardown: () => byteTransport.kill(),
14
- exited: byteTransport.exited,
15
- stderrTail: byteTransport.stderrTail
16
- };
17
- }
18
- return {
19
- stream: transport.stream,
20
- teardown: transport.dispose,
21
- exited: void 0,
22
- stderrTail: transport.stderrTail ?? (() => "")
23
- };
17
+ if (transport.kind === "stdio") {
18
+ const byteTransport = spawnHandleToAcpTransport(transport.process);
19
+ return {
20
+ stream: ndJsonStream(byteTransport.writable, byteTransport.readable),
21
+ teardown: () => byteTransport.kill(),
22
+ exited: byteTransport.exited,
23
+ stderrTail: byteTransport.stderrTail
24
+ };
25
+ }
26
+ return {
27
+ stream: transport.stream,
28
+ teardown: transport.dispose,
29
+ exited: void 0,
30
+ stderrTail: transport.stderrTail ?? (() => "")
31
+ };
24
32
  }
33
+ /**
34
+ * Drive an ACP harness over stdio or a pre-connected JSON-RPC stream.
35
+ */
25
36
  async function startAcpSession(options) {
26
- const { stream, teardown, exited, stderrTail } = streamFromTransport(
27
- options.transport
28
- );
29
- let replaying = false;
30
- const client = {
31
- requestPermission: async (params) => {
32
- const outcome = await options.onPermissionRequest(params);
33
- return { outcome };
34
- },
35
- sessionUpdate: (params) => {
36
- if (!replaying) {
37
- options.onUpdate(params.update);
38
- }
39
- return Promise.resolve();
40
- },
41
- extNotification: (method, params) => {
42
- options.onExtNotification?.(method, params);
43
- return Promise.resolve();
44
- }
45
- };
46
- const race = (work) => exited !== void 0 ? Promise.race([work, exited]) : work;
47
- try {
48
- const connection = new ClientSideConnection(() => client, stream);
49
- const initResult = await race(
50
- connection.initialize({
51
- protocolVersion: PROTOCOL_VERSION,
52
- clientInfo: CLIENT_INFO,
53
- clientCapabilities: {
54
- // The agent runs inside the sandbox with direct filesystem + shell
55
- // access, so it never needs to delegate file/terminal I/O back to the
56
- // client. We advertise these as unsupported; per the ACP spec the
57
- // agent MUST then treat them as unavailable and not call them.
58
- fs: { readTextFile: false, writeTextFile: false }
59
- }
60
- })
61
- );
62
- if (typeof initResult.protocolVersion === "number" && initResult.protocolVersion > PROTOCOL_VERSION) {
63
- throw new Error(
64
- `ACP agent negotiated protocol version ${initResult.protocolVersion}, but this client supports up to ${PROTOCOL_VERSION}. Update @tanstack/ai-acp.`
65
- );
66
- }
67
- if (options.authMethodId !== void 0) {
68
- const available = initResult.authMethods ?? [];
69
- if (!available.some((method) => method.id === options.authMethodId)) {
70
- throw new Error(
71
- `Harness does not advertise the ACP auth method '${options.authMethodId}'. Available: ${available.map((method) => method.id).join(", ") || "(none)"}.`
72
- );
73
- }
74
- await race(connection.authenticate({ methodId: options.authMethodId }));
75
- }
76
- const mcpServers = (options.mcpServers ?? []).map(
77
- (server) => ({
78
- type: "http",
79
- name: server.name,
80
- url: server.url,
81
- headers: server.headers ?? []
82
- })
83
- );
84
- let sessionId;
85
- let resumed = false;
86
- if (options.resumeSessionId !== void 0 && initResult.agentCapabilities?.loadSession === true) {
87
- replaying = true;
88
- try {
89
- await race(
90
- connection.loadSession({
91
- sessionId: options.resumeSessionId,
92
- cwd: options.cwd,
93
- mcpServers
94
- })
95
- );
96
- sessionId = options.resumeSessionId;
97
- resumed = true;
98
- } catch {
99
- } finally {
100
- replaying = false;
101
- }
102
- }
103
- if (sessionId === void 0) {
104
- const session = await race(
105
- connection.newSession({ cwd: options.cwd, mcpServers })
106
- );
107
- sessionId = session.sessionId;
108
- }
109
- return {
110
- sessionId,
111
- resumed,
112
- prompt: async (text) => {
113
- const response = await race(
114
- connection.prompt({
115
- sessionId,
116
- prompt: [{ type: "text", text }]
117
- })
118
- );
119
- return {
120
- stopReason: response.stopReason,
121
- ...response.usage != null && { usage: response.usage }
122
- };
123
- },
124
- cancel: () => connection.cancel({ sessionId }),
125
- dispose: teardown
126
- };
127
- } catch (error) {
128
- await teardown();
129
- const tail = stderrTail().trim();
130
- if (error instanceof Error && tail !== "" && !error.message.includes(tail)) {
131
- throw new Error(`${error.message}
132
- stderr: ${tail}`, { cause: error });
133
- }
134
- throw error;
135
- }
37
+ const { stream, teardown, exited, stderrTail } = streamFromTransport(options.transport);
38
+ let replaying = false;
39
+ const client = {
40
+ requestPermission: async (params) => {
41
+ return { outcome: await options.onPermissionRequest(params) };
42
+ },
43
+ sessionUpdate: (params) => {
44
+ if (!replaying) options.onUpdate(params.update);
45
+ return Promise.resolve();
46
+ },
47
+ extNotification: (method, params) => {
48
+ options.onExtNotification?.(method, params);
49
+ return Promise.resolve();
50
+ }
51
+ };
52
+ const race = (work) => exited !== void 0 ? Promise.race([work, exited]) : work;
53
+ try {
54
+ const connection = new ClientSideConnection(() => client, stream);
55
+ const initResult = await race(connection.initialize({
56
+ protocolVersion: PROTOCOL_VERSION,
57
+ clientInfo: CLIENT_INFO,
58
+ clientCapabilities: { fs: {
59
+ readTextFile: false,
60
+ writeTextFile: false
61
+ } }
62
+ }));
63
+ if (typeof initResult.protocolVersion === "number" && initResult.protocolVersion > PROTOCOL_VERSION) throw new Error(`ACP agent negotiated protocol version ${initResult.protocolVersion}, but this client supports up to ${PROTOCOL_VERSION}. Update @tanstack/ai-acp.`);
64
+ if (options.authMethodId !== void 0) {
65
+ const available = initResult.authMethods ?? [];
66
+ if (!available.some((method) => method.id === options.authMethodId)) throw new Error(`Harness does not advertise the ACP auth method '${options.authMethodId}'. Available: ${available.map((method) => method.id).join(", ") || "(none)"}.`);
67
+ await race(connection.authenticate({ methodId: options.authMethodId }));
68
+ }
69
+ const mcpServers = (options.mcpServers ?? []).map((server) => ({
70
+ type: "http",
71
+ name: server.name,
72
+ url: server.url,
73
+ headers: server.headers ?? []
74
+ }));
75
+ let sessionId;
76
+ let resumed = false;
77
+ if (options.resumeSessionId !== void 0 && initResult.agentCapabilities?.loadSession === true) {
78
+ replaying = true;
79
+ try {
80
+ await race(connection.loadSession({
81
+ sessionId: options.resumeSessionId,
82
+ cwd: options.cwd,
83
+ mcpServers
84
+ }));
85
+ sessionId = options.resumeSessionId;
86
+ resumed = true;
87
+ } catch {} finally {
88
+ replaying = false;
89
+ }
90
+ }
91
+ if (sessionId === void 0) sessionId = (await race(connection.newSession({
92
+ cwd: options.cwd,
93
+ mcpServers
94
+ }))).sessionId;
95
+ return {
96
+ sessionId,
97
+ resumed,
98
+ prompt: async (text) => {
99
+ const response = await race(connection.prompt({
100
+ sessionId,
101
+ prompt: [{
102
+ type: "text",
103
+ text
104
+ }]
105
+ }));
106
+ return {
107
+ stopReason: response.stopReason,
108
+ ...response.usage != null && { usage: response.usage }
109
+ };
110
+ },
111
+ cancel: () => connection.cancel({ sessionId }),
112
+ dispose: teardown
113
+ };
114
+ } catch (error) {
115
+ await teardown();
116
+ const tail = stderrTail().trim();
117
+ if (error instanceof Error && tail !== "" && !error.message.includes(tail)) throw new Error(`${error.message}\nstderr: ${tail}`, { cause: error });
118
+ throw error;
119
+ }
136
120
  }
137
- export {
138
- startAcpSession
139
- };
140
- //# sourceMappingURL=acp-client.js.map
121
+ //#endregion
122
+ export { startAcpSession };
123
+
124
+ //# sourceMappingURL=acp-client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"acp-client.js","sources":["../../../src/session/acp-client.ts"],"sourcesContent":["import {\n ClientSideConnection,\n PROTOCOL_VERSION,\n ndJsonStream,\n} from '@agentclientprotocol/sdk'\nimport { spawnHandleToAcpTransport } from '../transport/stdio'\nimport type {\n Client,\n McpServer,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from '@agentclientprotocol/sdk'\nimport type {\n AcpPermissionOutcome,\n AcpPermissionRequest,\n AcpSessionUpdate,\n AcpStopReason,\n AcpUsage,\n} from '../types/acp-types'\nimport type { AcpJsonRpcStream, AcpSessionTransport } from '../transport/types'\n\n/**\n * Identifies this client to the agent in the ACP `initialize` handshake. The\n * `version` is informational (sent in `clientInfo`); keep it loosely in step\n * with the package version. `clientInfo` is currently optional in the protocol\n * but will become required, so we always send it.\n * ponytail: hardcoded — clientInfo is diagnostic; a stale patch version is harmless.\n */\nconst CLIENT_INFO = {\n name: '@tanstack/ai-acp',\n title: 'TanStack AI',\n version: '0.1.0',\n} as const\n\nexport interface AcpSessionHandle {\n sessionId: string\n resumed: boolean\n prompt: (\n text: string,\n ) => Promise<{ stopReason: AcpStopReason; usage?: AcpUsage }>\n cancel: () => Promise<void>\n dispose: () => Promise<void>\n}\n\nexport interface StartAcpSessionOptions {\n transport: AcpSessionTransport\n cwd: string\n authMethodId?: string\n mcpServers?: Array<{\n name: string\n url: string\n headers?: Array<{ name: string; value: string }>\n }>\n resumeSessionId?: string\n onUpdate: (update: AcpSessionUpdate) => void\n onPermissionRequest: (\n request: AcpPermissionRequest,\n ) => Promise<AcpPermissionOutcome> | AcpPermissionOutcome\n /**\n * Harness-specific JSON-RPC notifications (e.g. Grok `_x.ai/session_notification`).\n * Return without throwing — unknown vendor extensions must not tear down the session.\n */\n onExtNotification?: (method: string, params: Record<string, unknown>) => void\n}\n\nfunction streamFromTransport(transport: AcpSessionTransport): {\n stream: AcpJsonRpcStream\n teardown: () => Promise<void>\n exited: Promise<never> | undefined\n stderrTail: () => string\n} {\n if (transport.kind === 'stdio') {\n const byteTransport = spawnHandleToAcpTransport(transport.process)\n return {\n stream: ndJsonStream(byteTransport.writable, byteTransport.readable),\n teardown: () => byteTransport.kill(),\n exited: byteTransport.exited,\n stderrTail: byteTransport.stderrTail,\n }\n }\n\n return {\n stream: transport.stream,\n teardown: transport.dispose,\n exited: undefined,\n stderrTail: transport.stderrTail ?? (() => ''),\n }\n}\n\n/**\n * Drive an ACP harness over stdio or a pre-connected JSON-RPC stream.\n */\nexport async function startAcpSession(\n options: StartAcpSessionOptions,\n): Promise<AcpSessionHandle> {\n const { stream, teardown, exited, stderrTail } = streamFromTransport(\n options.transport,\n )\n\n let replaying = false\n\n const client: Client = {\n requestPermission: async (\n params: RequestPermissionRequest,\n ): Promise<RequestPermissionResponse> => {\n const outcome = await options.onPermissionRequest(params)\n return { outcome }\n },\n sessionUpdate: (params: SessionNotification): Promise<void> => {\n if (!replaying) {\n options.onUpdate(params.update as AcpSessionUpdate)\n }\n return Promise.resolve()\n },\n extNotification: (method, params) => {\n options.onExtNotification?.(method, params)\n return Promise.resolve()\n },\n }\n\n const race = <T>(work: Promise<T>): Promise<T> =>\n exited !== undefined ? Promise.race([work, exited]) : work\n\n try {\n const connection = new ClientSideConnection(() => client, stream)\n\n const initResult = await race(\n connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientInfo: CLIENT_INFO,\n clientCapabilities: {\n // The agent runs inside the sandbox with direct filesystem + shell\n // access, so it never needs to delegate file/terminal I/O back to the\n // client. We advertise these as unsupported; per the ACP spec the\n // agent MUST then treat them as unavailable and not call them.\n fs: { readTextFile: false, writeTextFile: false },\n },\n }),\n )\n\n // Protocol-version negotiation: the agent echoes the version it will speak.\n // The spec says a client SHOULD close the connection if that version is one\n // it doesn't support. We only implement the current PROTOCOL_VERSION, so a\n // higher number means the agent needs a newer client than this one.\n if (\n typeof initResult.protocolVersion === 'number' &&\n initResult.protocolVersion > PROTOCOL_VERSION\n ) {\n throw new Error(\n `ACP agent negotiated protocol version ${initResult.protocolVersion}, ` +\n `but this client supports up to ${PROTOCOL_VERSION}. Update @tanstack/ai-acp.`,\n )\n }\n\n if (options.authMethodId !== undefined) {\n const available = initResult.authMethods ?? []\n if (!available.some((method) => method.id === options.authMethodId)) {\n throw new Error(\n `Harness does not advertise the ACP auth method '${options.authMethodId}'. Available: ${\n available.map((method) => method.id).join(', ') || '(none)'\n }.`,\n )\n }\n await race(connection.authenticate({ methodId: options.authMethodId }))\n }\n\n const mcpServers: Array<McpServer> = (options.mcpServers ?? []).map(\n (server) => ({\n type: 'http' as const,\n name: server.name,\n url: server.url,\n headers: server.headers ?? [],\n }),\n )\n\n let sessionId: string | undefined\n let resumed = false\n if (\n options.resumeSessionId !== undefined &&\n initResult.agentCapabilities?.loadSession === true\n ) {\n replaying = true\n try {\n await race(\n connection.loadSession({\n sessionId: options.resumeSessionId,\n cwd: options.cwd,\n mcpServers,\n }),\n )\n sessionId = options.resumeSessionId\n resumed = true\n } catch {\n // Session unknown — fall through to a fresh one.\n } finally {\n replaying = false\n }\n }\n\n if (sessionId === undefined) {\n const session = await race(\n connection.newSession({ cwd: options.cwd, mcpServers }),\n )\n sessionId = session.sessionId\n }\n\n return {\n sessionId,\n resumed,\n prompt: async (text: string) => {\n const response = await race(\n connection.prompt({\n sessionId,\n prompt: [{ type: 'text', text }],\n }),\n )\n return {\n stopReason: response.stopReason,\n ...(response.usage != null && { usage: response.usage }),\n }\n },\n cancel: () => connection.cancel({ sessionId }),\n dispose: teardown,\n }\n } catch (error) {\n await teardown()\n const tail = stderrTail().trim()\n if (\n error instanceof Error &&\n tail !== '' &&\n !error.message.includes(tail)\n ) {\n throw new Error(`${error.message}\\nstderr: ${tail}`, { cause: error })\n }\n throw error\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"acp-client.js","names":[],"sources":["../../../src/session/acp-client.ts"],"sourcesContent":["import {\n ClientSideConnection,\n PROTOCOL_VERSION,\n ndJsonStream,\n} from '@agentclientprotocol/sdk'\nimport { spawnHandleToAcpTransport } from '../transport/stdio'\nimport type {\n Client,\n McpServer,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n} from '@agentclientprotocol/sdk'\nimport type {\n AcpPermissionOutcome,\n AcpPermissionRequest,\n AcpSessionUpdate,\n AcpStopReason,\n AcpUsage,\n} from '../types/acp-types'\nimport type { AcpJsonRpcStream, AcpSessionTransport } from '../transport/types'\n\n/**\n * Identifies this client to the agent in the ACP `initialize` handshake. The\n * `version` is informational (sent in `clientInfo`); keep it loosely in step\n * with the package version. `clientInfo` is currently optional in the protocol\n * but will become required, so we always send it.\n * ponytail: hardcoded — clientInfo is diagnostic; a stale patch version is harmless.\n */\nconst CLIENT_INFO = {\n name: '@tanstack/ai-acp',\n title: 'TanStack AI',\n version: '0.1.0',\n} as const\n\nexport interface AcpSessionHandle {\n sessionId: string\n resumed: boolean\n prompt: (\n text: string,\n ) => Promise<{ stopReason: AcpStopReason; usage?: AcpUsage }>\n cancel: () => Promise<void>\n dispose: () => Promise<void>\n}\n\nexport interface StartAcpSessionOptions {\n transport: AcpSessionTransport\n cwd: string\n authMethodId?: string\n mcpServers?: Array<{\n name: string\n url: string\n headers?: Array<{ name: string; value: string }>\n }>\n resumeSessionId?: string\n onUpdate: (update: AcpSessionUpdate) => void\n onPermissionRequest: (\n request: AcpPermissionRequest,\n ) => Promise<AcpPermissionOutcome> | AcpPermissionOutcome\n /**\n * Harness-specific JSON-RPC notifications (e.g. Grok `_x.ai/session_notification`).\n * Return without throwing — unknown vendor extensions must not tear down the session.\n */\n onExtNotification?: (method: string, params: Record<string, unknown>) => void\n}\n\nfunction streamFromTransport(transport: AcpSessionTransport): {\n stream: AcpJsonRpcStream\n teardown: () => Promise<void>\n exited: Promise<never> | undefined\n stderrTail: () => string\n} {\n if (transport.kind === 'stdio') {\n const byteTransport = spawnHandleToAcpTransport(transport.process)\n return {\n stream: ndJsonStream(byteTransport.writable, byteTransport.readable),\n teardown: () => byteTransport.kill(),\n exited: byteTransport.exited,\n stderrTail: byteTransport.stderrTail,\n }\n }\n\n return {\n stream: transport.stream,\n teardown: transport.dispose,\n exited: undefined,\n stderrTail: transport.stderrTail ?? (() => ''),\n }\n}\n\n/**\n * Drive an ACP harness over stdio or a pre-connected JSON-RPC stream.\n */\nexport async function startAcpSession(\n options: StartAcpSessionOptions,\n): Promise<AcpSessionHandle> {\n const { stream, teardown, exited, stderrTail } = streamFromTransport(\n options.transport,\n )\n\n let replaying = false\n\n const client: Client = {\n requestPermission: async (\n params: RequestPermissionRequest,\n ): Promise<RequestPermissionResponse> => {\n const outcome = await options.onPermissionRequest(params)\n return { outcome }\n },\n sessionUpdate: (params: SessionNotification): Promise<void> => {\n if (!replaying) {\n options.onUpdate(params.update as AcpSessionUpdate)\n }\n return Promise.resolve()\n },\n extNotification: (method, params) => {\n options.onExtNotification?.(method, params)\n return Promise.resolve()\n },\n }\n\n const race = <T>(work: Promise<T>): Promise<T> =>\n exited !== undefined ? Promise.race([work, exited]) : work\n\n try {\n const connection = new ClientSideConnection(() => client, stream)\n\n const initResult = await race(\n connection.initialize({\n protocolVersion: PROTOCOL_VERSION,\n clientInfo: CLIENT_INFO,\n clientCapabilities: {\n // The agent runs inside the sandbox with direct filesystem + shell\n // access, so it never needs to delegate file/terminal I/O back to the\n // client. We advertise these as unsupported; per the ACP spec the\n // agent MUST then treat them as unavailable and not call them.\n fs: { readTextFile: false, writeTextFile: false },\n },\n }),\n )\n\n // Protocol-version negotiation: the agent echoes the version it will speak.\n // The spec says a client SHOULD close the connection if that version is one\n // it doesn't support. We only implement the current PROTOCOL_VERSION, so a\n // higher number means the agent needs a newer client than this one.\n if (\n typeof initResult.protocolVersion === 'number' &&\n initResult.protocolVersion > PROTOCOL_VERSION\n ) {\n throw new Error(\n `ACP agent negotiated protocol version ${initResult.protocolVersion}, ` +\n `but this client supports up to ${PROTOCOL_VERSION}. Update @tanstack/ai-acp.`,\n )\n }\n\n if (options.authMethodId !== undefined) {\n const available = initResult.authMethods ?? []\n if (!available.some((method) => method.id === options.authMethodId)) {\n throw new Error(\n `Harness does not advertise the ACP auth method '${options.authMethodId}'. Available: ${\n available.map((method) => method.id).join(', ') || '(none)'\n }.`,\n )\n }\n await race(connection.authenticate({ methodId: options.authMethodId }))\n }\n\n const mcpServers: Array<McpServer> = (options.mcpServers ?? []).map(\n (server) => ({\n type: 'http' as const,\n name: server.name,\n url: server.url,\n headers: server.headers ?? [],\n }),\n )\n\n let sessionId: string | undefined\n let resumed = false\n if (\n options.resumeSessionId !== undefined &&\n initResult.agentCapabilities?.loadSession === true\n ) {\n replaying = true\n try {\n await race(\n connection.loadSession({\n sessionId: options.resumeSessionId,\n cwd: options.cwd,\n mcpServers,\n }),\n )\n sessionId = options.resumeSessionId\n resumed = true\n } catch {\n // Session unknown — fall through to a fresh one.\n } finally {\n replaying = false\n }\n }\n\n if (sessionId === undefined) {\n const session = await race(\n connection.newSession({ cwd: options.cwd, mcpServers }),\n )\n sessionId = session.sessionId\n }\n\n return {\n sessionId,\n resumed,\n prompt: async (text: string) => {\n const response = await race(\n connection.prompt({\n sessionId,\n prompt: [{ type: 'text', text }],\n }),\n )\n return {\n stopReason: response.stopReason,\n ...(response.usage != null && { usage: response.usage }),\n }\n },\n cancel: () => connection.cancel({ sessionId }),\n dispose: teardown,\n }\n } catch (error) {\n await teardown()\n const tail = stderrTail().trim()\n if (\n error instanceof Error &&\n tail !== '' &&\n !error.message.includes(tail)\n ) {\n throw new Error(`${error.message}\\nstderr: ${tail}`, { cause: error })\n }\n throw error\n }\n}\n"],"mappings":"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"}
@@ -1,114 +1,102 @@
1
1
  import { connectAcpWebSocket } from "../transport/websocket.js";
2
- const DEFAULT_READY_MARKER = "WebSocket URL:";
2
+ //#region src/session/sandbox-server.ts
3
+ var DEFAULT_READY_MARKER = "WebSocket URL:";
3
4
  function waitForReady(proc, options) {
4
- const readyMarker = options.readyMarker ?? DEFAULT_READY_MARKER;
5
- const isReady = options.isReady ?? ((output) => output.includes(readyMarker));
6
- return new Promise((resolve, reject) => {
7
- let stdout = "";
8
- let stderr = "";
9
- const state = { settled: false };
10
- const settle = (fn) => {
11
- if (state.settled) return;
12
- state.settled = true;
13
- clearTimeout(timer);
14
- fn();
15
- };
16
- const diagnostics = () => [stdout, stderr].map((s) => s.trim()).filter(Boolean).join("\n").slice(-500);
17
- const combined = () => stdout + stderr;
18
- let stdoutDone = false;
19
- let stderrDone = false;
20
- const tryFinish = () => {
21
- if (state.settled) return;
22
- if (isReady(combined())) {
23
- settle(() => resolve({ stdout, stderr }));
24
- return;
25
- }
26
- if (stdoutDone && stderrDone) {
27
- settle(
28
- () => reject(
29
- new Error(
30
- `ACP server exited before becoming ready${diagnostics() ? `: ${diagnostics()}` : " (no output)"}`
31
- )
32
- )
33
- );
34
- }
35
- };
36
- const timer = setTimeout(
37
- () => settle(
38
- () => reject(
39
- new Error(
40
- `ACP server did not become ready within ${options.timeoutMs ?? 3e4}ms${diagnostics() ? `: ${diagnostics()}` : ""}`
41
- )
42
- )
43
- ),
44
- options.timeoutMs ?? 3e4
45
- );
46
- void (async () => {
47
- try {
48
- for await (const chunk of proc.stderr) {
49
- stderr += chunk;
50
- tryFinish();
51
- if (state.settled) return;
52
- }
53
- } catch {
54
- } finally {
55
- stderrDone = true;
56
- tryFinish();
57
- }
58
- })();
59
- void (async () => {
60
- try {
61
- for await (const chunk of proc.stdout) {
62
- stdout += chunk;
63
- tryFinish();
64
- if (state.settled) return;
65
- }
66
- } catch (error) {
67
- settle(() => reject(error));
68
- return;
69
- } finally {
70
- stdoutDone = true;
71
- tryFinish();
72
- }
73
- })();
74
- });
5
+ const readyMarker = options.readyMarker ?? DEFAULT_READY_MARKER;
6
+ const isReady = options.isReady ?? ((output) => output.includes(readyMarker));
7
+ return new Promise((resolve, reject) => {
8
+ let stdout = "";
9
+ let stderr = "";
10
+ const state = { settled: false };
11
+ const settle = (fn) => {
12
+ if (state.settled) return;
13
+ state.settled = true;
14
+ clearTimeout(timer);
15
+ fn();
16
+ };
17
+ const diagnostics = () => [stdout, stderr].map((s) => s.trim()).filter(Boolean).join("\n").slice(-500);
18
+ const combined = () => stdout + stderr;
19
+ let stdoutDone = false;
20
+ let stderrDone = false;
21
+ const tryFinish = () => {
22
+ if (state.settled) return;
23
+ if (isReady(combined())) {
24
+ settle(() => resolve({
25
+ stdout,
26
+ stderr
27
+ }));
28
+ return;
29
+ }
30
+ if (stdoutDone && stderrDone) settle(() => reject(/* @__PURE__ */ new Error(`ACP server exited before becoming ready${diagnostics() ? `: ${diagnostics()}` : " (no output)"}`)));
31
+ };
32
+ const timer = setTimeout(() => settle(() => reject(/* @__PURE__ */ new Error(`ACP server did not become ready within ${options.timeoutMs ?? 3e4}ms${diagnostics() ? `: ${diagnostics()}` : ""}`))), options.timeoutMs ?? 3e4);
33
+ (async () => {
34
+ try {
35
+ for await (const chunk of proc.stderr) {
36
+ stderr += chunk;
37
+ tryFinish();
38
+ if (state.settled) return;
39
+ }
40
+ } catch {} finally {
41
+ stderrDone = true;
42
+ tryFinish();
43
+ }
44
+ })();
45
+ (async () => {
46
+ try {
47
+ for await (const chunk of proc.stdout) {
48
+ stdout += chunk;
49
+ tryFinish();
50
+ if (state.settled) return;
51
+ }
52
+ } catch (error) {
53
+ settle(() => reject(error));
54
+ return;
55
+ } finally {
56
+ stdoutDone = true;
57
+ tryFinish();
58
+ }
59
+ })();
60
+ });
75
61
  }
62
+ /**
63
+ * Boot a harness ACP WebSocket server inside a sandbox and expose its port via
64
+ * {@link SandboxHandle.ports.connect}. Mirrors the `opencode serve` pattern.
65
+ */
76
66
  async function startAcpServerInSandbox(sandbox, options) {
77
- const proc = await sandbox.process.spawn(options.command, {
78
- cwd: options.cwd,
79
- ...options.env ? { env: options.env } : {},
80
- ...options.signal ? { signal: options.signal } : {}
81
- });
82
- const { stdout } = await waitForReady(proc, options);
83
- const channel = await sandbox.ports.connect(options.port);
84
- const wsUrl = options.buildWsUrl({
85
- channel,
86
- port: options.port,
87
- stdout
88
- });
89
- const headers = channel.headers ?? (channel.token ? { Authorization: `Bearer ${channel.token}` } : void 0);
90
- return {
91
- wsUrl,
92
- channel,
93
- connect: (signal) => connectAcpWebSocket(wsUrl, {
94
- ...headers ? { headers } : {},
95
- ...signal ? { signal } : {},
96
- framing: options.framing ?? "frame"
97
- }),
98
- dispose: () => proc.kill()
99
- };
67
+ const proc = await sandbox.process.spawn(options.command, {
68
+ cwd: options.cwd,
69
+ ...options.env ? { env: options.env } : {},
70
+ ...options.signal ? { signal: options.signal } : {}
71
+ });
72
+ const { stdout } = await waitForReady(proc, options);
73
+ const channel = await sandbox.ports.connect(options.port);
74
+ const wsUrl = options.buildWsUrl({
75
+ channel,
76
+ port: options.port,
77
+ stdout
78
+ });
79
+ const headers = channel.headers ?? (channel.token ? { Authorization: `Bearer ${channel.token}` } : void 0);
80
+ return {
81
+ wsUrl,
82
+ channel,
83
+ connect: (signal) => connectAcpWebSocket(wsUrl, {
84
+ ...headers ? { headers } : {},
85
+ ...signal ? { signal } : {},
86
+ framing: options.framing ?? "frame"
87
+ }),
88
+ dispose: () => proc.kill()
89
+ };
100
90
  }
91
+ /** Parse a `WebSocket URL: ws://…` line printed by `grok agent serve`. */
101
92
  function parseWebSocketUrlFromServeOutput(output) {
102
- const match = output.match(/WebSocket URL:\s*(ws\S+)/i);
103
- return match?.[1];
93
+ return output.match(/WebSocket URL:\s*(ws\S+)/i)?.[1];
104
94
  }
95
+ /** Build a Grok `agent serve` WebSocket URL from a sandbox channel + secret. */
105
96
  function buildGrokServeWebSocketUrl(channelUrl, secret) {
106
- const wsBase = channelUrl.replace(/^http/i, "ws").replace(/\/$/, "");
107
- return `${wsBase}/ws?server-key=${encodeURIComponent(secret)}`;
97
+ return `${channelUrl.replace(/^http/i, "ws").replace(/\/$/, "")}/ws?server-key=${encodeURIComponent(secret)}`;
108
98
  }
109
- export {
110
- buildGrokServeWebSocketUrl,
111
- parseWebSocketUrlFromServeOutput,
112
- startAcpServerInSandbox
113
- };
114
- //# sourceMappingURL=sandbox-server.js.map
99
+ //#endregion
100
+ export { buildGrokServeWebSocketUrl, parseWebSocketUrlFromServeOutput, startAcpServerInSandbox };
101
+
102
+ //# sourceMappingURL=sandbox-server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-server.js","sources":["../../../src/session/sandbox-server.ts"],"sourcesContent":["import { connectAcpWebSocket } from '../transport/websocket'\nimport type {\n SandboxChannel,\n SandboxHandle,\n SpawnHandle,\n} from '@tanstack/ai-sandbox'\nimport type { AcpMessageFraming } from '../transport/types'\nimport type { AcpWebSocketConnection } from '../transport/websocket'\n\nexport interface AcpSandboxServer {\n /** WebSocket URL the orchestrator uses to reach the in-sandbox ACP server. */\n wsUrl: string\n /** Sandbox channel used to build {@link wsUrl} (auth headers, when issued). */\n channel: SandboxChannel\n /** Open the ACP JSON-RPC stream over WebSocket. */\n connect: (signal?: AbortSignal) => Promise<AcpWebSocketConnection>\n /** Stop the in-sandbox server process. */\n dispose: () => Promise<void>\n}\n\nexport interface StartAcpServerOptions {\n port: number\n cwd: string\n /** Full shell command that starts the harness ACP server inside the sandbox. */\n command: string\n /** Build the WebSocket URL once the server is ready and the port is exposed. */\n buildWsUrl: (input: {\n channel: SandboxChannel\n port: number\n stdout: string\n }) => string\n /**\n * Return true once {@link buildWsUrl} can be called. Receives accumulated\n * stdout from the server process.\n */\n isReady?: (stdout: string) => boolean\n /** Substring/regex marker used when {@link isReady} is omitted. */\n readyMarker?: string\n env?: Record<string, string>\n timeoutMs?: number\n signal?: AbortSignal\n framing?: AcpMessageFraming\n hostname?: string\n}\n\nconst DEFAULT_READY_MARKER = 'WebSocket URL:'\n\nfunction waitForReady(\n proc: SpawnHandle,\n options: Pick<StartAcpServerOptions, 'isReady' | 'readyMarker' | 'timeoutMs'>,\n): Promise<{ stdout: string; stderr: string }> {\n const readyMarker = options.readyMarker ?? DEFAULT_READY_MARKER\n const isReady =\n options.isReady ?? ((output: string) => output.includes(readyMarker))\n\n return new Promise((resolve, reject) => {\n let stdout = ''\n let stderr = ''\n const state = { settled: false }\n const settle = (fn: () => void): void => {\n if (state.settled) return\n state.settled = true\n clearTimeout(timer)\n fn()\n }\n const diagnostics = (): string =>\n [stdout, stderr]\n .map((s) => s.trim())\n .filter(Boolean)\n .join('\\n')\n .slice(-500)\n const combined = (): string => stdout + stderr\n let stdoutDone = false\n let stderrDone = false\n const tryFinish = (): void => {\n if (state.settled) return\n if (isReady(combined())) {\n settle(() => resolve({ stdout, stderr }))\n return\n }\n if (stdoutDone && stderrDone) {\n settle(() =>\n reject(\n new Error(\n `ACP server exited before becoming ready${\n diagnostics() ? `: ${diagnostics()}` : ' (no output)'\n }`,\n ),\n ),\n )\n }\n }\n\n const timer = setTimeout(\n () =>\n settle(() =>\n reject(\n new Error(\n `ACP server did not become ready within ${options.timeoutMs ?? 30_000}ms${\n diagnostics() ? `: ${diagnostics()}` : ''\n }`,\n ),\n ),\n ),\n options.timeoutMs ?? 30_000,\n )\n\n void (async () => {\n try {\n for await (const chunk of proc.stderr) {\n stderr += chunk\n tryFinish()\n if (state.settled) return\n }\n } catch {\n // non-fatal\n } finally {\n stderrDone = true\n tryFinish()\n }\n })()\n\n void (async () => {\n try {\n for await (const chunk of proc.stdout) {\n stdout += chunk\n tryFinish()\n if (state.settled) return\n }\n } catch (error) {\n settle(() => reject(error))\n return\n } finally {\n stdoutDone = true\n tryFinish()\n }\n })()\n })\n}\n\n/**\n * Boot a harness ACP WebSocket server inside a sandbox and expose its port via\n * {@link SandboxHandle.ports.connect}. Mirrors the `opencode serve` pattern.\n */\nexport async function startAcpServerInSandbox(\n sandbox: SandboxHandle,\n options: StartAcpServerOptions,\n): Promise<AcpSandboxServer> {\n const proc = await sandbox.process.spawn(options.command, {\n cwd: options.cwd,\n ...(options.env ? { env: options.env } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n })\n\n const { stdout } = await waitForReady(proc, options)\n const channel = await sandbox.ports.connect(options.port)\n const wsUrl = options.buildWsUrl({\n channel,\n port: options.port,\n stdout,\n })\n\n const headers =\n channel.headers ??\n (channel.token ? { Authorization: `Bearer ${channel.token}` } : undefined)\n\n return {\n wsUrl,\n channel,\n connect: (signal?: AbortSignal) =>\n connectAcpWebSocket(wsUrl, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {}),\n framing: options.framing ?? 'frame',\n }),\n dispose: () => proc.kill(),\n }\n}\n\n/** Parse a `WebSocket URL: ws://…` line printed by `grok agent serve`. */\nexport function parseWebSocketUrlFromServeOutput(\n output: string,\n): string | undefined {\n const match = output.match(/WebSocket URL:\\s*(ws\\S+)/i)\n return match?.[1]\n}\n\n/** Build a Grok `agent serve` WebSocket URL from a sandbox channel + secret. */\nexport function buildGrokServeWebSocketUrl(\n channelUrl: string,\n secret: string,\n): string {\n const wsBase = channelUrl.replace(/^http/i, 'ws').replace(/\\/$/, '')\n return `${wsBase}/ws?server-key=${encodeURIComponent(secret)}`\n}\n"],"names":[],"mappings":";AA6CA,MAAM,uBAAuB;AAE7B,SAAS,aACP,MACA,SAC6C;AAC7C,QAAM,cAAc,QAAQ,eAAe;AAC3C,QAAM,UACJ,QAAQ,YAAY,CAAC,WAAmB,OAAO,SAAS,WAAW;AAErE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,QAAQ,EAAE,SAAS,MAAA;AACzB,UAAM,SAAS,CAAC,OAAyB;AACvC,UAAI,MAAM,QAAS;AACnB,YAAM,UAAU;AAChB,mBAAa,KAAK;AAClB,SAAA;AAAA,IACF;AACA,UAAM,cAAc,MAClB,CAAC,QAAQ,MAAM,EACZ,IAAI,CAAC,MAAM,EAAE,MAAM,EACnB,OAAO,OAAO,EACd,KAAK,IAAI,EACT,MAAM,IAAI;AACf,UAAM,WAAW,MAAc,SAAS;AACxC,QAAI,aAAa;AACjB,QAAI,aAAa;AACjB,UAAM,YAAY,MAAY;AAC5B,UAAI,MAAM,QAAS;AACnB,UAAI,QAAQ,SAAA,CAAU,GAAG;AACvB,eAAO,MAAM,QAAQ,EAAE,QAAQ,OAAA,CAAQ,CAAC;AACxC;AAAA,MACF;AACA,UAAI,cAAc,YAAY;AAC5B;AAAA,UAAO,MACL;AAAA,YACE,IAAI;AAAA,cACF,0CACE,gBAAgB,KAAK,YAAA,CAAa,KAAK,cACzC;AAAA,YAAA;AAAA,UACF;AAAA,QACF;AAAA,MAEJ;AAAA,IACF;AAEA,UAAM,QAAQ;AAAA,MACZ,MACE;AAAA,QAAO,MACL;AAAA,UACE,IAAI;AAAA,YACF,0CAA0C,QAAQ,aAAa,GAAM,KACnE,YAAA,IAAgB,KAAK,aAAa,KAAK,EACzC;AAAA,UAAA;AAAA,QACF;AAAA,MACF;AAAA,MAEJ,QAAQ,aAAa;AAAA,IAAA;AAGvB,UAAM,YAAY;AAChB,UAAI;AACF,yBAAiB,SAAS,KAAK,QAAQ;AACrC,oBAAU;AACV,oBAAA;AACA,cAAI,MAAM,QAAS;AAAA,QACrB;AAAA,MACF,QAAQ;AAAA,MAER,UAAA;AACE,qBAAa;AACb,kBAAA;AAAA,MACF;AAAA,IACF,GAAA;AAEA,UAAM,YAAY;AAChB,UAAI;AACF,yBAAiB,SAAS,KAAK,QAAQ;AACrC,oBAAU;AACV,oBAAA;AACA,cAAI,MAAM,QAAS;AAAA,QACrB;AAAA,MACF,SAAS,OAAO;AACd,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B;AAAA,MACF,UAAA;AACE,qBAAa;AACb,kBAAA;AAAA,MACF;AAAA,IACF,GAAA;AAAA,EACF,CAAC;AACH;AAMA,eAAsB,wBACpB,SACA,SAC2B;AAC3B,QAAM,OAAO,MAAM,QAAQ,QAAQ,MAAM,QAAQ,SAAS;AAAA,IACxD,KAAK,QAAQ;AAAA,IACb,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAA,IAAQ,CAAA;AAAA,IACzC,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAA,IAAW,CAAA;AAAA,EAAC,CACpD;AAED,QAAM,EAAE,OAAA,IAAW,MAAM,aAAa,MAAM,OAAO;AACnD,QAAM,UAAU,MAAM,QAAQ,MAAM,QAAQ,QAAQ,IAAI;AACxD,QAAM,QAAQ,QAAQ,WAAW;AAAA,IAC/B;AAAA,IACA,MAAM,QAAQ;AAAA,IACd;AAAA,EAAA,CACD;AAED,QAAM,UACJ,QAAQ,YACP,QAAQ,QAAQ,EAAE,eAAe,UAAU,QAAQ,KAAK,GAAA,IAAO;AAElE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,CAAC,WACR,oBAAoB,OAAO;AAAA,MACzB,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,MAC5B,GAAI,SAAS,EAAE,OAAA,IAAW,CAAA;AAAA,MAC1B,SAAS,QAAQ,WAAW;AAAA,IAAA,CAC7B;AAAA,IACH,SAAS,MAAM,KAAK,KAAA;AAAA,EAAK;AAE7B;AAGO,SAAS,iCACd,QACoB;AACpB,QAAM,QAAQ,OAAO,MAAM,2BAA2B;AACtD,SAAO,QAAQ,CAAC;AAClB;AAGO,SAAS,2BACd,YACA,QACQ;AACR,QAAM,SAAS,WAAW,QAAQ,UAAU,IAAI,EAAE,QAAQ,OAAO,EAAE;AACnE,SAAO,GAAG,MAAM,kBAAkB,mBAAmB,MAAM,CAAC;AAC9D;"}
1
+ {"version":3,"file":"sandbox-server.js","names":[],"sources":["../../../src/session/sandbox-server.ts"],"sourcesContent":["import { connectAcpWebSocket } from '../transport/websocket'\nimport type {\n SandboxChannel,\n SandboxHandle,\n SpawnHandle,\n} from '@tanstack/ai-sandbox'\nimport type { AcpMessageFraming } from '../transport/types'\nimport type { AcpWebSocketConnection } from '../transport/websocket'\n\nexport interface AcpSandboxServer {\n /** WebSocket URL the orchestrator uses to reach the in-sandbox ACP server. */\n wsUrl: string\n /** Sandbox channel used to build {@link wsUrl} (auth headers, when issued). */\n channel: SandboxChannel\n /** Open the ACP JSON-RPC stream over WebSocket. */\n connect: (signal?: AbortSignal) => Promise<AcpWebSocketConnection>\n /** Stop the in-sandbox server process. */\n dispose: () => Promise<void>\n}\n\nexport interface StartAcpServerOptions {\n port: number\n cwd: string\n /** Full shell command that starts the harness ACP server inside the sandbox. */\n command: string\n /** Build the WebSocket URL once the server is ready and the port is exposed. */\n buildWsUrl: (input: {\n channel: SandboxChannel\n port: number\n stdout: string\n }) => string\n /**\n * Return true once {@link buildWsUrl} can be called. Receives accumulated\n * stdout from the server process.\n */\n isReady?: (stdout: string) => boolean\n /** Substring/regex marker used when {@link isReady} is omitted. */\n readyMarker?: string\n env?: Record<string, string>\n timeoutMs?: number\n signal?: AbortSignal\n framing?: AcpMessageFraming\n hostname?: string\n}\n\nconst DEFAULT_READY_MARKER = 'WebSocket URL:'\n\nfunction waitForReady(\n proc: SpawnHandle,\n options: Pick<StartAcpServerOptions, 'isReady' | 'readyMarker' | 'timeoutMs'>,\n): Promise<{ stdout: string; stderr: string }> {\n const readyMarker = options.readyMarker ?? DEFAULT_READY_MARKER\n const isReady =\n options.isReady ?? ((output: string) => output.includes(readyMarker))\n\n return new Promise((resolve, reject) => {\n let stdout = ''\n let stderr = ''\n const state = { settled: false }\n const settle = (fn: () => void): void => {\n if (state.settled) return\n state.settled = true\n clearTimeout(timer)\n fn()\n }\n const diagnostics = (): string =>\n [stdout, stderr]\n .map((s) => s.trim())\n .filter(Boolean)\n .join('\\n')\n .slice(-500)\n const combined = (): string => stdout + stderr\n let stdoutDone = false\n let stderrDone = false\n const tryFinish = (): void => {\n if (state.settled) return\n if (isReady(combined())) {\n settle(() => resolve({ stdout, stderr }))\n return\n }\n if (stdoutDone && stderrDone) {\n settle(() =>\n reject(\n new Error(\n `ACP server exited before becoming ready${\n diagnostics() ? `: ${diagnostics()}` : ' (no output)'\n }`,\n ),\n ),\n )\n }\n }\n\n const timer = setTimeout(\n () =>\n settle(() =>\n reject(\n new Error(\n `ACP server did not become ready within ${options.timeoutMs ?? 30_000}ms${\n diagnostics() ? `: ${diagnostics()}` : ''\n }`,\n ),\n ),\n ),\n options.timeoutMs ?? 30_000,\n )\n\n void (async () => {\n try {\n for await (const chunk of proc.stderr) {\n stderr += chunk\n tryFinish()\n if (state.settled) return\n }\n } catch {\n // non-fatal\n } finally {\n stderrDone = true\n tryFinish()\n }\n })()\n\n void (async () => {\n try {\n for await (const chunk of proc.stdout) {\n stdout += chunk\n tryFinish()\n if (state.settled) return\n }\n } catch (error) {\n settle(() => reject(error))\n return\n } finally {\n stdoutDone = true\n tryFinish()\n }\n })()\n })\n}\n\n/**\n * Boot a harness ACP WebSocket server inside a sandbox and expose its port via\n * {@link SandboxHandle.ports.connect}. Mirrors the `opencode serve` pattern.\n */\nexport async function startAcpServerInSandbox(\n sandbox: SandboxHandle,\n options: StartAcpServerOptions,\n): Promise<AcpSandboxServer> {\n const proc = await sandbox.process.spawn(options.command, {\n cwd: options.cwd,\n ...(options.env ? { env: options.env } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n })\n\n const { stdout } = await waitForReady(proc, options)\n const channel = await sandbox.ports.connect(options.port)\n const wsUrl = options.buildWsUrl({\n channel,\n port: options.port,\n stdout,\n })\n\n const headers =\n channel.headers ??\n (channel.token ? { Authorization: `Bearer ${channel.token}` } : undefined)\n\n return {\n wsUrl,\n channel,\n connect: (signal?: AbortSignal) =>\n connectAcpWebSocket(wsUrl, {\n ...(headers ? { headers } : {}),\n ...(signal ? { signal } : {}),\n framing: options.framing ?? 'frame',\n }),\n dispose: () => proc.kill(),\n }\n}\n\n/** Parse a `WebSocket URL: ws://…` line printed by `grok agent serve`. */\nexport function parseWebSocketUrlFromServeOutput(\n output: string,\n): string | undefined {\n const match = output.match(/WebSocket URL:\\s*(ws\\S+)/i)\n return match?.[1]\n}\n\n/** Build a Grok `agent serve` WebSocket URL from a sandbox channel + secret. */\nexport function buildGrokServeWebSocketUrl(\n channelUrl: string,\n secret: string,\n): string {\n const wsBase = channelUrl.replace(/^http/i, 'ws').replace(/\\/$/, '')\n return `${wsBase}/ws?server-key=${encodeURIComponent(secret)}`\n}\n"],"mappings":"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"}