@tanstack/ai-opencode 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.
- package/dist/esm/adapters/projection.js +140 -67
- package/dist/esm/adapters/projection.js.map +1 -1
- package/dist/esm/adapters/text.js +194 -220
- package/dist/esm/adapters/text.js.map +1 -1
- package/dist/esm/index.js +6 -21
- package/dist/esm/messages/prompt.js +27 -33
- package/dist/esm/messages/prompt.js.map +1 -1
- package/dist/esm/model-meta.js +23 -12
- package/dist/esm/model-meta.js.map +1 -1
- package/dist/esm/process/permissions.js +56 -44
- package/dist/esm/process/permissions.js.map +1 -1
- package/dist/esm/process/sandbox-server.js +52 -68
- package/dist/esm/process/sandbox-server.js.map +1 -1
- package/dist/esm/process/server.js +162 -154
- package/dist/esm/process/server.js.map +1 -1
- package/dist/esm/stream/queue.js +60 -55
- package/dist/esm/stream/queue.js.map +1 -1
- package/dist/esm/stream/translate.js +311 -296
- package/dist/esm/stream/translate.js.map +1 -1
- package/package.json +7 -7
- package/src/adapters/text.ts +50 -1
- package/dist/esm/index.js.map +0 -1
|
@@ -1,160 +1,168 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createOpencode, createOpencodeClient } from "@opencode-ai/sdk";
|
|
2
|
+
//#region src/process/server.ts
|
|
3
|
+
/** Locate the session id an OpenCode event belongs to, when it carries one. */
|
|
2
4
|
function sessionIdOf(event) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (event.type === "message.updated") {
|
|
9
|
-
return event.properties.info.sessionID;
|
|
10
|
-
}
|
|
11
|
-
if (event.type === "permission.updated") {
|
|
12
|
-
return event.properties.sessionID;
|
|
13
|
-
}
|
|
14
|
-
return void 0;
|
|
5
|
+
const props = event.properties;
|
|
6
|
+
if (props?.sessionID !== void 0) return props.sessionID;
|
|
7
|
+
if (event.type === "message.part.updated") return event.properties.part.sessionID;
|
|
8
|
+
if (event.type === "message.updated") return event.properties.info.sessionID;
|
|
9
|
+
if (event.type === "permission.updated") return event.properties.sessionID;
|
|
15
10
|
}
|
|
16
11
|
function buildConfig(options) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
12
|
+
const mcp = { ...options.config?.mcp };
|
|
13
|
+
for (const server of options.mcpServers ?? []) mcp[server.name] = {
|
|
14
|
+
type: "remote",
|
|
15
|
+
url: server.url,
|
|
16
|
+
enabled: true
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
...options.config,
|
|
20
|
+
...Object.keys(mcp).length > 0 && { mcp },
|
|
21
|
+
...options.permission !== void 0 && { permission: options.permission }
|
|
22
|
+
};
|
|
26
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Boot (or attach to) an OpenCode HTTP server, resolve a session, and wire its
|
|
26
|
+
* event subscription + permission replies.
|
|
27
|
+
*
|
|
28
|
+
* This module is the only place that touches `@opencode-ai/sdk`; the rest of
|
|
29
|
+
* the package works with the structural types in `sdk-types.ts`.
|
|
30
|
+
*
|
|
31
|
+
* Resume semantics: when `resumeSessionId` is set and the server still knows
|
|
32
|
+
* the session (same machine, same data dir), it is reused. Otherwise a fresh
|
|
33
|
+
* session is created and `resumed: false` tells the adapter to send the
|
|
34
|
+
* flattened transcript.
|
|
35
|
+
*/
|
|
27
36
|
async function startOpencodeSession(options) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
37
|
+
const { directory } = options;
|
|
38
|
+
const dirQuery = directory !== void 0 ? { directory } : {};
|
|
39
|
+
let client;
|
|
40
|
+
let ownedServer;
|
|
41
|
+
if (options.baseUrl !== void 0) client = createOpencodeClient({
|
|
42
|
+
baseUrl: options.baseUrl,
|
|
43
|
+
...options.headers !== void 0 && { headers: options.headers },
|
|
44
|
+
...directory !== void 0 && { directory }
|
|
45
|
+
});
|
|
46
|
+
else {
|
|
47
|
+
const config = buildConfig(options);
|
|
48
|
+
const result = await createOpencode({
|
|
49
|
+
...options.hostname !== void 0 && { hostname: options.hostname },
|
|
50
|
+
...options.port !== void 0 && { port: options.port },
|
|
51
|
+
...Object.keys(config).length > 0 && { config }
|
|
52
|
+
});
|
|
53
|
+
client = result.client;
|
|
54
|
+
ownedServer = result.server;
|
|
55
|
+
}
|
|
56
|
+
const lifecycle = { disposed: false };
|
|
57
|
+
const teardown = async () => {
|
|
58
|
+
if (lifecycle.disposed) return;
|
|
59
|
+
lifecycle.disposed = true;
|
|
60
|
+
ownedServer?.close();
|
|
61
|
+
await Promise.resolve();
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
let sessionId;
|
|
65
|
+
let resumed = false;
|
|
66
|
+
if (options.resumeSessionId !== void 0) {
|
|
67
|
+
if ((await client.session.get({
|
|
68
|
+
path: { id: options.resumeSessionId },
|
|
69
|
+
query: dirQuery
|
|
70
|
+
})).data) {
|
|
71
|
+
sessionId = options.resumeSessionId;
|
|
72
|
+
resumed = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (sessionId === void 0) sessionId = (await client.session.create({
|
|
76
|
+
query: dirQuery,
|
|
77
|
+
body: {},
|
|
78
|
+
throwOnError: true
|
|
79
|
+
})).data.id;
|
|
80
|
+
const resolvedSessionId = sessionId;
|
|
81
|
+
const handlePermission = async (permission) => {
|
|
82
|
+
try {
|
|
83
|
+
const response = await options.onPermissionRequest({
|
|
84
|
+
id: permission.id,
|
|
85
|
+
sessionID: permission.sessionID,
|
|
86
|
+
type: permission.type,
|
|
87
|
+
title: permission.title,
|
|
88
|
+
...permission.callID !== void 0 && { callID: permission.callID }
|
|
89
|
+
});
|
|
90
|
+
await client.postSessionIdPermissionsPermissionId({
|
|
91
|
+
path: {
|
|
92
|
+
id: permission.sessionID,
|
|
93
|
+
permissionID: permission.id
|
|
94
|
+
},
|
|
95
|
+
query: dirQuery,
|
|
96
|
+
body: { response },
|
|
97
|
+
throwOnError: true
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (!lifecycle.disposed) options.onError?.(error);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const stream = (await client.event.subscribe()).stream;
|
|
104
|
+
(async () => {
|
|
105
|
+
try {
|
|
106
|
+
for await (const event of stream) {
|
|
107
|
+
if (lifecycle.disposed) break;
|
|
108
|
+
const sid = sessionIdOf(event);
|
|
109
|
+
if (sid !== void 0 && sid !== resolvedSessionId) continue;
|
|
110
|
+
if (event.type === "permission.updated") {
|
|
111
|
+
handlePermission(event.properties);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
options.onEvent(event);
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (!lifecycle.disposed) options.onError?.(error);
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
return {
|
|
121
|
+
sessionId: resolvedSessionId,
|
|
122
|
+
resumed,
|
|
123
|
+
prompt: async (text) => {
|
|
124
|
+
const data = (await client.session.prompt({
|
|
125
|
+
path: { id: resolvedSessionId },
|
|
126
|
+
query: dirQuery,
|
|
127
|
+
body: {
|
|
128
|
+
model: {
|
|
129
|
+
providerID: options.providerID,
|
|
130
|
+
modelID: options.modelID
|
|
131
|
+
},
|
|
132
|
+
parts: [{
|
|
133
|
+
type: "text",
|
|
134
|
+
text
|
|
135
|
+
}]
|
|
136
|
+
},
|
|
137
|
+
throwOnError: true
|
|
138
|
+
})).data;
|
|
139
|
+
return {
|
|
140
|
+
message: data.info,
|
|
141
|
+
text: data.parts.filter((part) => part.type === "text").map((part) => part.text).join("")
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
abort: async () => {
|
|
145
|
+
try {
|
|
146
|
+
await client.session.abort({
|
|
147
|
+
path: { id: resolvedSessionId },
|
|
148
|
+
query: dirQuery
|
|
149
|
+
});
|
|
150
|
+
} catch {}
|
|
151
|
+
},
|
|
152
|
+
dispose: async () => {
|
|
153
|
+
lifecycle.disposed = true;
|
|
154
|
+
try {
|
|
155
|
+
await stream.return(void 0);
|
|
156
|
+
} catch {}
|
|
157
|
+
ownedServer?.close();
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
await teardown();
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
156
164
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//# sourceMappingURL=server.js.map
|
|
165
|
+
//#endregion
|
|
166
|
+
export { startOpencodeSession };
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../../../src/process/server.ts"],"sourcesContent":["import { createOpencode, createOpencodeClient } from '@opencode-ai/sdk'\nimport type { Config, Event, OpencodeClient, Part } from '@opencode-ai/sdk'\nimport type {\n OpencodeAssistantMessage,\n OpencodeEvent,\n} from '../stream/sdk-types'\nimport type {\n OpencodePermissionRequest,\n OpencodePermissionResponse,\n} from './permissions'\n\n/** A live OpenCode session backed by an `opencode serve` HTTP server. */\nexport interface OpencodeSessionHandle {\n sessionId: string\n /** Whether an existing session was actually resumed. */\n resumed: boolean\n /**\n * Run one prompt turn. Resolves with the final assistant message (finish\n * reason, token usage, error) and its concatenated text once the harness\n * goes idle. Streaming deltas arrive via `onEvent` while this is pending.\n */\n prompt: (\n text: string,\n ) => Promise<{ message: OpencodeAssistantMessage; text: string }>\n /** Ask the harness to abort the in-flight prompt turn. */\n abort: () => Promise<void>\n /** Tear down the event subscription and (if owned) the server. */\n dispose: () => Promise<void>\n}\n\nexport interface StartOpencodeSessionOptions {\n /** Connect to an already-running server instead of spawning one. */\n baseUrl?: string\n /**\n * Headers attached to every request to the opencode server — used to\n * authenticate a token-gated preview channel (e.g. Daytona). Without them a\n * gated preview proxy rejects the requests (404 \"Not found.\").\n */\n headers?: Record<string, string>\n /** Hostname for the spawned server. Defaults to the SDK default. */\n hostname?: string\n /** Port for the spawned server. Defaults to the SDK default. */\n port?: number\n /**\n * Directory the opencode HTTP API scopes the session to. Omit to use the\n * server's own launch cwd (the common case): the server is spawned with the\n * correct working dir per-provider, so passing a directory here is only needed\n * to override it. Passing a VIRTUAL sandbox path (e.g. `/workspace`) is wrong\n * for host-running providers (local-process), where that path doesn't exist —\n * the API then stalls on it. Leave undefined and rely on the server cwd.\n */\n directory?: string\n /** Provider id (the part before `/` in the model id). */\n providerID: string\n /** Model id (the part after `/` in the model id). */\n modelID: string\n /** Extra OpenCode config merged with the adapter's mcp/permission config. */\n config?: Config\n /** Baseline permission policy applied to the spawned server. */\n permission?: Config['permission']\n /** MCP servers (e.g. the TanStack tool bridge) for the session. */\n mcpServers?: Array<{ name: string; url: string }>\n /** Session id to resume; falls back to a fresh session when not found. */\n resumeSessionId?: string\n onEvent: (event: OpencodeEvent) => void\n onPermissionRequest: (\n request: OpencodePermissionRequest,\n ) => Promise<OpencodePermissionResponse> | OpencodePermissionResponse\n /** Called when the event subscription fails mid-turn. */\n onError?: (error: unknown) => void\n}\n\n/** Locate the session id an OpenCode event belongs to, when it carries one. */\nfunction sessionIdOf(event: Event): string | undefined {\n const props = event.properties as { sessionID?: string } | undefined\n if (props?.sessionID !== undefined) return props.sessionID\n if (event.type === 'message.part.updated') {\n return event.properties.part.sessionID\n }\n if (event.type === 'message.updated') {\n return event.properties.info.sessionID\n }\n if (event.type === 'permission.updated') {\n return event.properties.sessionID\n }\n return undefined\n}\n\nfunction buildConfig(options: StartOpencodeSessionOptions): Config {\n const mcp: NonNullable<Config['mcp']> = { ...options.config?.mcp }\n for (const server of options.mcpServers ?? []) {\n mcp[server.name] = { type: 'remote', url: server.url, enabled: true }\n }\n return {\n ...options.config,\n ...(Object.keys(mcp).length > 0 && { mcp }),\n ...(options.permission !== undefined && { permission: options.permission }),\n }\n}\n\n/**\n * Boot (or attach to) an OpenCode HTTP server, resolve a session, and wire its\n * event subscription + permission replies.\n *\n * This module is the only place that touches `@opencode-ai/sdk`; the rest of\n * the package works with the structural types in `sdk-types.ts`.\n *\n * Resume semantics: when `resumeSessionId` is set and the server still knows\n * the session (same machine, same data dir), it is reused. Otherwise a fresh\n * session is created and `resumed: false` tells the adapter to send the\n * flattened transcript.\n */\nexport async function startOpencodeSession(\n options: StartOpencodeSessionOptions,\n): Promise<OpencodeSessionHandle> {\n const { directory } = options\n // Spread into a `query` object only when set; omitting lets opencode use the\n // server's launch cwd (correct for every provider — see `directory` docs).\n const dirQuery = directory !== undefined ? { directory } : {}\n\n let client: OpencodeClient\n let ownedServer: { close: () => void } | undefined\n\n if (options.baseUrl !== undefined) {\n client = createOpencodeClient({\n baseUrl: options.baseUrl,\n ...(options.headers !== undefined && { headers: options.headers }),\n ...(directory !== undefined && { directory }),\n })\n } else {\n const config = buildConfig(options)\n const result = await createOpencode({\n ...(options.hostname !== undefined && { hostname: options.hostname }),\n ...(options.port !== undefined && { port: options.port }),\n ...(Object.keys(config).length > 0 && { config }),\n })\n client = result.client\n ownedServer = result.server\n }\n\n // Mutated from several closures (the subscription loop, dispose, teardown);\n // a holder object keeps reads typed as `boolean` rather than being\n // flow-narrowed to a literal across those boundaries.\n const lifecycle = { disposed: false }\n\n const teardown = async (): Promise<void> => {\n if (lifecycle.disposed) return\n lifecycle.disposed = true\n ownedServer?.close()\n await Promise.resolve()\n }\n\n try {\n // Resolve the session before subscribing so the event filter has an id.\n let sessionId: string | undefined\n let resumed = false\n if (options.resumeSessionId !== undefined) {\n const existing = await client.session.get({\n path: { id: options.resumeSessionId },\n query: dirQuery,\n })\n if (existing.data) {\n sessionId = options.resumeSessionId\n resumed = true\n }\n }\n if (sessionId === undefined) {\n const created = await client.session.create({\n query: dirQuery,\n body: {},\n throwOnError: true,\n })\n sessionId = created.data.id\n }\n const resolvedSessionId = sessionId\n\n const handlePermission = async (\n permission: Extract<Event, { type: 'permission.updated' }>['properties'],\n ): Promise<void> => {\n try {\n const response = await options.onPermissionRequest({\n id: permission.id,\n sessionID: permission.sessionID,\n type: permission.type,\n title: permission.title,\n ...(permission.callID !== undefined && { callID: permission.callID }),\n })\n await client.postSessionIdPermissionsPermissionId({\n path: { id: permission.sessionID, permissionID: permission.id },\n query: dirQuery,\n body: { response },\n throwOnError: true,\n })\n } catch (error) {\n if (!lifecycle.disposed) options.onError?.(error)\n }\n }\n\n const subscription = await client.event.subscribe()\n const stream = subscription.stream\n\n void (async () => {\n try {\n for await (const event of stream) {\n if (lifecycle.disposed) break\n const sid = sessionIdOf(event)\n if (sid !== undefined && sid !== resolvedSessionId) continue\n if (event.type === 'permission.updated') {\n void handlePermission(event.properties)\n continue\n }\n // The SDK event union is a structural superset of the subset the\n // translator consumes; unknown event types match no translator\n // branch and are ignored.\n options.onEvent(event as OpencodeEvent)\n }\n } catch (error) {\n if (!lifecycle.disposed) options.onError?.(error)\n }\n })()\n\n return {\n sessionId: resolvedSessionId,\n resumed,\n prompt: async (text: string) => {\n const result = await client.session.prompt({\n path: { id: resolvedSessionId },\n query: dirQuery,\n body: {\n model: { providerID: options.providerID, modelID: options.modelID },\n parts: [{ type: 'text', text }],\n },\n throwOnError: true,\n })\n const data = result.data\n const message = data.info as OpencodeAssistantMessage\n const responseText = data.parts\n .filter(\n (part): part is Extract<Part, { type: 'text' }> =>\n part.type === 'text',\n )\n .map((part) => part.text)\n .join('')\n return { message, text: responseText }\n },\n abort: async () => {\n try {\n await client.session.abort({\n path: { id: resolvedSessionId },\n query: dirQuery,\n })\n } catch {\n // Best-effort: the turn may already be finishing.\n }\n },\n dispose: async () => {\n lifecycle.disposed = true\n try {\n await stream.return(undefined)\n } catch {\n // Ignore: stream may already be closed.\n }\n ownedServer?.close()\n },\n }\n } catch (error) {\n await teardown()\n throw error\n }\n}\n"],"names":[],"mappings":";AAyEA,SAAS,YAAY,OAAkC;AACrD,QAAM,QAAQ,MAAM;AACpB,MAAI,OAAO,cAAc,OAAW,QAAO,MAAM;AACjD,MAAI,MAAM,SAAS,wBAAwB;AACzC,WAAO,MAAM,WAAW,KAAK;AAAA,EAC/B;AACA,MAAI,MAAM,SAAS,mBAAmB;AACpC,WAAO,MAAM,WAAW,KAAK;AAAA,EAC/B;AACA,MAAI,MAAM,SAAS,sBAAsB;AACvC,WAAO,MAAM,WAAW;AAAA,EAC1B;AACA,SAAO;AACT;AAEA,SAAS,YAAY,SAA8C;AACjE,QAAM,MAAkC,EAAE,GAAG,QAAQ,QAAQ,IAAA;AAC7D,aAAW,UAAU,QAAQ,cAAc,CAAA,GAAI;AAC7C,QAAI,OAAO,IAAI,IAAI,EAAE,MAAM,UAAU,KAAK,OAAO,KAAK,SAAS,KAAA;AAAA,EACjE;AACA,SAAO;AAAA,IACL,GAAG,QAAQ;AAAA,IACX,GAAI,OAAO,KAAK,GAAG,EAAE,SAAS,KAAK,EAAE,IAAA;AAAA,IACrC,GAAI,QAAQ,eAAe,UAAa,EAAE,YAAY,QAAQ,WAAA;AAAA,EAAW;AAE7E;AAcA,eAAsB,qBACpB,SACgC;AAChC,QAAM,EAAE,cAAc;AAGtB,QAAM,WAAW,cAAc,SAAY,EAAE,UAAA,IAAc,CAAA;AAE3D,MAAI;AACJ,MAAI;AAEJ,MAAI,QAAQ,YAAY,QAAW;AACjC,aAAS,qBAAqB;AAAA,MAC5B,SAAS,QAAQ;AAAA,MACjB,GAAI,QAAQ,YAAY,UAAa,EAAE,SAAS,QAAQ,QAAA;AAAA,MACxD,GAAI,cAAc,UAAa,EAAE,UAAA;AAAA,IAAU,CAC5C;AAAA,EACH,OAAO;AACL,UAAM,SAAS,YAAY,OAAO;AAClC,UAAM,SAAS,MAAM,eAAe;AAAA,MAClC,GAAI,QAAQ,aAAa,UAAa,EAAE,UAAU,QAAQ,SAAA;AAAA,MAC1D,GAAI,QAAQ,SAAS,UAAa,EAAE,MAAM,QAAQ,KAAA;AAAA,MAClD,GAAI,OAAO,KAAK,MAAM,EAAE,SAAS,KAAK,EAAE,OAAA;AAAA,IAAO,CAChD;AACD,aAAS,OAAO;AAChB,kBAAc,OAAO;AAAA,EACvB;AAKA,QAAM,YAAY,EAAE,UAAU,MAAA;AAE9B,QAAM,WAAW,YAA2B;AAC1C,QAAI,UAAU,SAAU;AACxB,cAAU,WAAW;AACrB,iBAAa,MAAA;AACb,UAAM,QAAQ,QAAA;AAAA,EAChB;AAEA,MAAI;AAEF,QAAI;AACJ,QAAI,UAAU;AACd,QAAI,QAAQ,oBAAoB,QAAW;AACzC,YAAM,WAAW,MAAM,OAAO,QAAQ,IAAI;AAAA,QACxC,MAAM,EAAE,IAAI,QAAQ,gBAAA;AAAA,QACpB,OAAO;AAAA,MAAA,CACR;AACD,UAAI,SAAS,MAAM;AACjB,oBAAY,QAAQ;AACpB,kBAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,cAAc,QAAW;AAC3B,YAAM,UAAU,MAAM,OAAO,QAAQ,OAAO;AAAA,QAC1C,OAAO;AAAA,QACP,MAAM,CAAA;AAAA,QACN,cAAc;AAAA,MAAA,CACf;AACD,kBAAY,QAAQ,KAAK;AAAA,IAC3B;AACA,UAAM,oBAAoB;AAE1B,UAAM,mBAAmB,OACvB,eACkB;AAClB,UAAI;AACF,cAAM,WAAW,MAAM,QAAQ,oBAAoB;AAAA,UACjD,IAAI,WAAW;AAAA,UACf,WAAW,WAAW;AAAA,UACtB,MAAM,WAAW;AAAA,UACjB,OAAO,WAAW;AAAA,UAClB,GAAI,WAAW,WAAW,UAAa,EAAE,QAAQ,WAAW,OAAA;AAAA,QAAO,CACpE;AACD,cAAM,OAAO,qCAAqC;AAAA,UAChD,MAAM,EAAE,IAAI,WAAW,WAAW,cAAc,WAAW,GAAA;AAAA,UAC3D,OAAO;AAAA,UACP,MAAM,EAAE,SAAA;AAAA,UACR,cAAc;AAAA,QAAA,CACf;AAAA,MACH,SAAS,OAAO;AACd,YAAI,CAAC,UAAU,SAAU,SAAQ,UAAU,KAAK;AAAA,MAClD;AAAA,IACF;AAEA,UAAM,eAAe,MAAM,OAAO,MAAM,UAAA;AACxC,UAAM,SAAS,aAAa;AAE5B,UAAM,YAAY;AAChB,UAAI;AACF,yBAAiB,SAAS,QAAQ;AAChC,cAAI,UAAU,SAAU;AACxB,gBAAM,MAAM,YAAY,KAAK;AAC7B,cAAI,QAAQ,UAAa,QAAQ,kBAAmB;AACpD,cAAI,MAAM,SAAS,sBAAsB;AACvC,iBAAK,iBAAiB,MAAM,UAAU;AACtC;AAAA,UACF;AAIA,kBAAQ,QAAQ,KAAsB;AAAA,QACxC;AAAA,MACF,SAAS,OAAO;AACd,YAAI,CAAC,UAAU,SAAU,SAAQ,UAAU,KAAK;AAAA,MAClD;AAAA,IACF,GAAA;AAEA,WAAO;AAAA,MACL,WAAW;AAAA,MACX;AAAA,MACA,QAAQ,OAAO,SAAiB;AAC9B,cAAM,SAAS,MAAM,OAAO,QAAQ,OAAO;AAAA,UACzC,MAAM,EAAE,IAAI,kBAAA;AAAA,UACZ,OAAO;AAAA,UACP,MAAM;AAAA,YACJ,OAAO,EAAE,YAAY,QAAQ,YAAY,SAAS,QAAQ,QAAA;AAAA,YAC1D,OAAO,CAAC,EAAE,MAAM,QAAQ,MAAM;AAAA,UAAA;AAAA,UAEhC,cAAc;AAAA,QAAA,CACf;AACD,cAAM,OAAO,OAAO;AACpB,cAAM,UAAU,KAAK;AACrB,cAAM,eAAe,KAAK,MACvB;AAAA,UACC,CAAC,SACC,KAAK,SAAS;AAAA,QAAA,EAEjB,IAAI,CAAC,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AACV,eAAO,EAAE,SAAS,MAAM,aAAA;AAAA,MAC1B;AAAA,MACA,OAAO,YAAY;AACjB,YAAI;AACF,gBAAM,OAAO,QAAQ,MAAM;AAAA,YACzB,MAAM,EAAE,IAAI,kBAAA;AAAA,YACZ,OAAO;AAAA,UAAA,CACR;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,MACA,SAAS,YAAY;AACnB,kBAAU,WAAW;AACrB,YAAI;AACF,gBAAM,OAAO,OAAO,MAAS;AAAA,QAC/B,QAAQ;AAAA,QAER;AACA,qBAAa,MAAA;AAAA,MACf;AAAA,IAAA;AAAA,EAEJ,SAAS,OAAO;AACd,UAAM,SAAA;AACN,UAAM;AAAA,EACR;AACF;"}
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../../src/process/server.ts"],"sourcesContent":["import { createOpencode, createOpencodeClient } from '@opencode-ai/sdk'\nimport type { Config, Event, OpencodeClient, Part } from '@opencode-ai/sdk'\nimport type {\n OpencodeAssistantMessage,\n OpencodeEvent,\n} from '../stream/sdk-types'\nimport type {\n OpencodePermissionRequest,\n OpencodePermissionResponse,\n} from './permissions'\n\n/** A live OpenCode session backed by an `opencode serve` HTTP server. */\nexport interface OpencodeSessionHandle {\n sessionId: string\n /** Whether an existing session was actually resumed. */\n resumed: boolean\n /**\n * Run one prompt turn. Resolves with the final assistant message (finish\n * reason, token usage, error) and its concatenated text once the harness\n * goes idle. Streaming deltas arrive via `onEvent` while this is pending.\n */\n prompt: (\n text: string,\n ) => Promise<{ message: OpencodeAssistantMessage; text: string }>\n /** Ask the harness to abort the in-flight prompt turn. */\n abort: () => Promise<void>\n /** Tear down the event subscription and (if owned) the server. */\n dispose: () => Promise<void>\n}\n\nexport interface StartOpencodeSessionOptions {\n /** Connect to an already-running server instead of spawning one. */\n baseUrl?: string\n /**\n * Headers attached to every request to the opencode server — used to\n * authenticate a token-gated preview channel (e.g. Daytona). Without them a\n * gated preview proxy rejects the requests (404 \"Not found.\").\n */\n headers?: Record<string, string>\n /** Hostname for the spawned server. Defaults to the SDK default. */\n hostname?: string\n /** Port for the spawned server. Defaults to the SDK default. */\n port?: number\n /**\n * Directory the opencode HTTP API scopes the session to. Omit to use the\n * server's own launch cwd (the common case): the server is spawned with the\n * correct working dir per-provider, so passing a directory here is only needed\n * to override it. Passing a VIRTUAL sandbox path (e.g. `/workspace`) is wrong\n * for host-running providers (local-process), where that path doesn't exist —\n * the API then stalls on it. Leave undefined and rely on the server cwd.\n */\n directory?: string\n /** Provider id (the part before `/` in the model id). */\n providerID: string\n /** Model id (the part after `/` in the model id). */\n modelID: string\n /** Extra OpenCode config merged with the adapter's mcp/permission config. */\n config?: Config\n /** Baseline permission policy applied to the spawned server. */\n permission?: Config['permission']\n /** MCP servers (e.g. the TanStack tool bridge) for the session. */\n mcpServers?: Array<{ name: string; url: string }>\n /** Session id to resume; falls back to a fresh session when not found. */\n resumeSessionId?: string\n onEvent: (event: OpencodeEvent) => void\n onPermissionRequest: (\n request: OpencodePermissionRequest,\n ) => Promise<OpencodePermissionResponse> | OpencodePermissionResponse\n /** Called when the event subscription fails mid-turn. */\n onError?: (error: unknown) => void\n}\n\n/** Locate the session id an OpenCode event belongs to, when it carries one. */\nfunction sessionIdOf(event: Event): string | undefined {\n const props = event.properties as { sessionID?: string } | undefined\n if (props?.sessionID !== undefined) return props.sessionID\n if (event.type === 'message.part.updated') {\n return event.properties.part.sessionID\n }\n if (event.type === 'message.updated') {\n return event.properties.info.sessionID\n }\n if (event.type === 'permission.updated') {\n return event.properties.sessionID\n }\n return undefined\n}\n\nfunction buildConfig(options: StartOpencodeSessionOptions): Config {\n const mcp: NonNullable<Config['mcp']> = { ...options.config?.mcp }\n for (const server of options.mcpServers ?? []) {\n mcp[server.name] = { type: 'remote', url: server.url, enabled: true }\n }\n return {\n ...options.config,\n ...(Object.keys(mcp).length > 0 && { mcp }),\n ...(options.permission !== undefined && { permission: options.permission }),\n }\n}\n\n/**\n * Boot (or attach to) an OpenCode HTTP server, resolve a session, and wire its\n * event subscription + permission replies.\n *\n * This module is the only place that touches `@opencode-ai/sdk`; the rest of\n * the package works with the structural types in `sdk-types.ts`.\n *\n * Resume semantics: when `resumeSessionId` is set and the server still knows\n * the session (same machine, same data dir), it is reused. Otherwise a fresh\n * session is created and `resumed: false` tells the adapter to send the\n * flattened transcript.\n */\nexport async function startOpencodeSession(\n options: StartOpencodeSessionOptions,\n): Promise<OpencodeSessionHandle> {\n const { directory } = options\n // Spread into a `query` object only when set; omitting lets opencode use the\n // server's launch cwd (correct for every provider — see `directory` docs).\n const dirQuery = directory !== undefined ? { directory } : {}\n\n let client: OpencodeClient\n let ownedServer: { close: () => void } | undefined\n\n if (options.baseUrl !== undefined) {\n client = createOpencodeClient({\n baseUrl: options.baseUrl,\n ...(options.headers !== undefined && { headers: options.headers }),\n ...(directory !== undefined && { directory }),\n })\n } else {\n const config = buildConfig(options)\n const result = await createOpencode({\n ...(options.hostname !== undefined && { hostname: options.hostname }),\n ...(options.port !== undefined && { port: options.port }),\n ...(Object.keys(config).length > 0 && { config }),\n })\n client = result.client\n ownedServer = result.server\n }\n\n // Mutated from several closures (the subscription loop, dispose, teardown);\n // a holder object keeps reads typed as `boolean` rather than being\n // flow-narrowed to a literal across those boundaries.\n const lifecycle = { disposed: false }\n\n const teardown = async (): Promise<void> => {\n if (lifecycle.disposed) return\n lifecycle.disposed = true\n ownedServer?.close()\n await Promise.resolve()\n }\n\n try {\n // Resolve the session before subscribing so the event filter has an id.\n let sessionId: string | undefined\n let resumed = false\n if (options.resumeSessionId !== undefined) {\n const existing = await client.session.get({\n path: { id: options.resumeSessionId },\n query: dirQuery,\n })\n if (existing.data) {\n sessionId = options.resumeSessionId\n resumed = true\n }\n }\n if (sessionId === undefined) {\n const created = await client.session.create({\n query: dirQuery,\n body: {},\n throwOnError: true,\n })\n sessionId = created.data.id\n }\n const resolvedSessionId = sessionId\n\n const handlePermission = async (\n permission: Extract<Event, { type: 'permission.updated' }>['properties'],\n ): Promise<void> => {\n try {\n const response = await options.onPermissionRequest({\n id: permission.id,\n sessionID: permission.sessionID,\n type: permission.type,\n title: permission.title,\n ...(permission.callID !== undefined && { callID: permission.callID }),\n })\n await client.postSessionIdPermissionsPermissionId({\n path: { id: permission.sessionID, permissionID: permission.id },\n query: dirQuery,\n body: { response },\n throwOnError: true,\n })\n } catch (error) {\n if (!lifecycle.disposed) options.onError?.(error)\n }\n }\n\n const subscription = await client.event.subscribe()\n const stream = subscription.stream\n\n void (async () => {\n try {\n for await (const event of stream) {\n if (lifecycle.disposed) break\n const sid = sessionIdOf(event)\n if (sid !== undefined && sid !== resolvedSessionId) continue\n if (event.type === 'permission.updated') {\n void handlePermission(event.properties)\n continue\n }\n // The SDK event union is a structural superset of the subset the\n // translator consumes; unknown event types match no translator\n // branch and are ignored.\n options.onEvent(event as OpencodeEvent)\n }\n } catch (error) {\n if (!lifecycle.disposed) options.onError?.(error)\n }\n })()\n\n return {\n sessionId: resolvedSessionId,\n resumed,\n prompt: async (text: string) => {\n const result = await client.session.prompt({\n path: { id: resolvedSessionId },\n query: dirQuery,\n body: {\n model: { providerID: options.providerID, modelID: options.modelID },\n parts: [{ type: 'text', text }],\n },\n throwOnError: true,\n })\n const data = result.data\n const message = data.info as OpencodeAssistantMessage\n const responseText = data.parts\n .filter(\n (part): part is Extract<Part, { type: 'text' }> =>\n part.type === 'text',\n )\n .map((part) => part.text)\n .join('')\n return { message, text: responseText }\n },\n abort: async () => {\n try {\n await client.session.abort({\n path: { id: resolvedSessionId },\n query: dirQuery,\n })\n } catch {\n // Best-effort: the turn may already be finishing.\n }\n },\n dispose: async () => {\n lifecycle.disposed = true\n try {\n await stream.return(undefined)\n } catch {\n // Ignore: stream may already be closed.\n }\n ownedServer?.close()\n },\n }\n } catch (error) {\n await teardown()\n throw error\n }\n}\n"],"mappings":"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"}
|
package/dist/esm/stream/queue.js
CHANGED
|
@@ -1,56 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
//#region src/stream/queue.ts
|
|
2
|
+
/**
|
|
3
|
+
* Minimal promise-based async queue bridging the OpenCode event
|
|
4
|
+
* subscription's callback-style notifications into the async-iterable world
|
|
5
|
+
* the stream translator consumes.
|
|
6
|
+
*/
|
|
7
|
+
var AsyncQueue = class {
|
|
8
|
+
values = [];
|
|
9
|
+
waiters = [];
|
|
10
|
+
ended = false;
|
|
11
|
+
error = void 0;
|
|
12
|
+
failed = false;
|
|
13
|
+
push(value) {
|
|
14
|
+
if (this.ended || this.failed) return;
|
|
15
|
+
const waiter = this.waiters.shift();
|
|
16
|
+
if (waiter) waiter.resolve({
|
|
17
|
+
value,
|
|
18
|
+
done: false
|
|
19
|
+
});
|
|
20
|
+
else this.values.push(value);
|
|
21
|
+
}
|
|
22
|
+
/** Signal normal completion; pending and future reads resolve as done. */
|
|
23
|
+
end() {
|
|
24
|
+
if (this.ended || this.failed) return;
|
|
25
|
+
this.ended = true;
|
|
26
|
+
for (const waiter of this.waiters.splice(0)) waiter.resolve({
|
|
27
|
+
value: void 0,
|
|
28
|
+
done: true
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Signal failure; pending and future reads reject (after buffered values drain). */
|
|
32
|
+
fail(error) {
|
|
33
|
+
if (this.ended || this.failed) return;
|
|
34
|
+
this.failed = true;
|
|
35
|
+
this.error = error;
|
|
36
|
+
for (const waiter of this.waiters.splice(0)) waiter.reject(error);
|
|
37
|
+
}
|
|
38
|
+
[Symbol.asyncIterator]() {
|
|
39
|
+
return { next: () => {
|
|
40
|
+
if (this.values.length > 0) return Promise.resolve({
|
|
41
|
+
value: this.values.shift(),
|
|
42
|
+
done: false
|
|
43
|
+
});
|
|
44
|
+
if (this.failed) return Promise.reject(this.error);
|
|
45
|
+
if (this.ended) return Promise.resolve({
|
|
46
|
+
value: void 0,
|
|
47
|
+
done: true
|
|
48
|
+
});
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
this.waiters.push({
|
|
51
|
+
resolve,
|
|
52
|
+
reject
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
} };
|
|
56
|
+
}
|
|
55
57
|
};
|
|
56
|
-
//#
|
|
58
|
+
//#endregion
|
|
59
|
+
export { AsyncQueue };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue.js","sources":["../../../src/stream/queue.ts"],"sourcesContent":["/**\n * Minimal promise-based async queue bridging the OpenCode event\n * subscription's callback-style notifications into the async-iterable world\n * the stream translator consumes.\n */\nexport class AsyncQueue<T> implements AsyncIterable<T> {\n private readonly values: Array<T> = []\n private readonly waiters: Array<{\n resolve: (result: IteratorResult<T>) => void\n reject: (error: unknown) => void\n }> = []\n private ended = false\n private error: unknown = undefined\n private failed = false\n\n push(value: T): void {\n if (this.ended || this.failed) return\n const waiter = this.waiters.shift()\n if (waiter) {\n waiter.resolve({ value, done: false })\n } else {\n this.values.push(value)\n }\n }\n\n /** Signal normal completion; pending and future reads resolve as done. */\n end(): void {\n if (this.ended || this.failed) return\n this.ended = true\n for (const waiter of this.waiters.splice(0)) {\n waiter.resolve({ value: undefined, done: true })\n }\n }\n\n /** Signal failure; pending and future reads reject (after buffered values drain). */\n fail(error: unknown): void {\n if (this.ended || this.failed) return\n this.failed = true\n this.error = error\n for (const waiter of this.waiters.splice(0)) {\n waiter.reject(error)\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n if (this.values.length > 0) {\n return Promise.resolve({\n value: this.values.shift() as T,\n done: false,\n })\n }\n if (this.failed) return Promise.reject(this.error)\n if (this.ended) {\n return Promise.resolve({ value: undefined, done: true })\n }\n return new Promise((resolve, reject) => {\n this.waiters.push({ resolve, reject })\n })\n },\n }\n }\n}\n"],"
|
|
1
|
+
{"version":3,"file":"queue.js","names":[],"sources":["../../../src/stream/queue.ts"],"sourcesContent":["/**\n * Minimal promise-based async queue bridging the OpenCode event\n * subscription's callback-style notifications into the async-iterable world\n * the stream translator consumes.\n */\nexport class AsyncQueue<T> implements AsyncIterable<T> {\n private readonly values: Array<T> = []\n private readonly waiters: Array<{\n resolve: (result: IteratorResult<T>) => void\n reject: (error: unknown) => void\n }> = []\n private ended = false\n private error: unknown = undefined\n private failed = false\n\n push(value: T): void {\n if (this.ended || this.failed) return\n const waiter = this.waiters.shift()\n if (waiter) {\n waiter.resolve({ value, done: false })\n } else {\n this.values.push(value)\n }\n }\n\n /** Signal normal completion; pending and future reads resolve as done. */\n end(): void {\n if (this.ended || this.failed) return\n this.ended = true\n for (const waiter of this.waiters.splice(0)) {\n waiter.resolve({ value: undefined, done: true })\n }\n }\n\n /** Signal failure; pending and future reads reject (after buffered values drain). */\n fail(error: unknown): void {\n if (this.ended || this.failed) return\n this.failed = true\n this.error = error\n for (const waiter of this.waiters.splice(0)) {\n waiter.reject(error)\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n if (this.values.length > 0) {\n return Promise.resolve({\n value: this.values.shift() as T,\n done: false,\n })\n }\n if (this.failed) return Promise.reject(this.error)\n if (this.ended) {\n return Promise.resolve({ value: undefined, done: true })\n }\n return new Promise((resolve, reject) => {\n this.waiters.push({ resolve, reject })\n })\n },\n }\n }\n}\n"],"mappings":";;;;;;AAKA,IAAa,aAAb,MAAuD;CACrD,SAAoC,CAAC;CACrC,UAGK,CAAC;CACN,QAAgB;CAChB,QAAyB,KAAA;CACzB,SAAiB;CAEjB,KAAK,OAAgB;EACnB,IAAI,KAAK,SAAS,KAAK,QAAQ;EAC/B,MAAM,SAAS,KAAK,QAAQ,MAAM;EAClC,IAAI,QACF,OAAO,QAAQ;GAAE;GAAO,MAAM;EAAM,CAAC;OAErC,KAAK,OAAO,KAAK,KAAK;CAE1B;;CAGA,MAAY;EACV,IAAI,KAAK,SAAS,KAAK,QAAQ;EAC/B,KAAK,QAAQ;EACb,KAAK,MAAM,UAAU,KAAK,QAAQ,OAAO,CAAC,GACxC,OAAO,QAAQ;GAAE,OAAO,KAAA;GAAW,MAAM;EAAK,CAAC;CAEnD;;CAGA,KAAK,OAAsB;EACzB,IAAI,KAAK,SAAS,KAAK,QAAQ;EAC/B,KAAK,SAAS;EACd,KAAK,QAAQ;EACb,KAAK,MAAM,UAAU,KAAK,QAAQ,OAAO,CAAC,GACxC,OAAO,OAAO,KAAK;CAEvB;CAEA,CAAC,OAAO,iBAAmC;EACzC,OAAO,EACL,YAAwC;GACtC,IAAI,KAAK,OAAO,SAAS,GACvB,OAAO,QAAQ,QAAQ;IACrB,OAAO,KAAK,OAAO,MAAM;IACzB,MAAM;GACR,CAAC;GAEH,IAAI,KAAK,QAAQ,OAAO,QAAQ,OAAO,KAAK,KAAK;GACjD,IAAI,KAAK,OACP,OAAO,QAAQ,QAAQ;IAAE,OAAO,KAAA;IAAW,MAAM;GAAK,CAAC;GAEzD,OAAO,IAAI,SAAS,SAAS,WAAW;IACtC,KAAK,QAAQ,KAAK;KAAE;KAAS;IAAO,CAAC;GACvC,CAAC;EACH,EACF;CACF;AACF"}
|