@getpaseo/server 0.9.2 → 0.10.0-beta.1
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/scripts/supervisor.js +51 -21
- package/dist/server/server/agent/agent-manager.js +22 -2
- package/dist/server/server/agent/agent-prompt.js +12 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts +3 -0
- package/dist/server/server/agent/mcp-shared.d.ts +5 -1
- package/dist/server/server/agent/mcp-shared.js +2 -1
- package/dist/server/server/agent/provider-registry.js +2 -2
- package/dist/server/server/agent/providers/acp-agent.js +20 -11
- package/dist/server/server/agent/providers/claude/agent.d.ts +1 -2
- package/dist/server/server/agent/providers/claude/agent.js +10 -9
- package/dist/server/server/agent/providers/claude/models.d.ts +1 -1
- package/dist/server/server/agent/providers/claude/models.js +1 -5
- package/dist/server/server/agent/providers/claude/project-dir.d.ts +1 -0
- package/dist/server/server/agent/providers/claude/project-dir.js +5 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +6 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +10 -1
- package/dist/server/server/agent/providers/generic-acp-agent.js +3 -1
- package/dist/server/server/agent/providers/omp/agent.js +23 -6
- package/dist/server/server/agent/providers/omp/rpc-types.d.ts +1 -0
- package/dist/server/server/agent/providers/omp/rpc-types.js +1 -0
- package/dist/server/server/agent/providers/opencode/bridge.d.ts +5 -1
- package/dist/server/server/agent/providers/opencode/bridge.js +56 -5
- package/dist/server/server/agent/providers/opencode/http-error.d.ts +8 -0
- package/dist/server/server/agent/providers/opencode/http-error.js +13 -0
- package/dist/server/server/agent/providers/opencode/runtime-client.d.ts +38 -0
- package/dist/server/server/agent/providers/opencode/runtime-client.js +131 -0
- package/dist/server/server/agent/providers/opencode/runtime-notice.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/runtime-notice.js +47 -0
- package/dist/server/server/agent/providers/opencode/server-manager.js +19 -8
- package/dist/server/server/agent/providers/opencode/v2/agent.d.ts +36 -0
- package/dist/server/server/agent/providers/opencode/v2/agent.js +208 -0
- package/dist/server/server/agent/providers/opencode/v2/api.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/v2/api.js +2 -0
- package/dist/server/server/agent/providers/opencode/v2/bridge-plugin.bundle.mjs +6693 -0
- package/dist/server/server/agent/providers/opencode/v2/capabilities.d.ts +3 -0
- package/dist/server/server/agent/providers/opencode/v2/capabilities.js +13 -0
- package/dist/server/server/agent/providers/opencode/v2/children.d.ts +20 -0
- package/dist/server/server/agent/providers/opencode/v2/children.js +75 -0
- package/dist/server/server/agent/providers/opencode/v2/commands.d.ts +4 -0
- package/dist/server/server/agent/providers/opencode/v2/commands.js +36 -0
- package/dist/server/server/agent/providers/opencode/v2/configuration.d.ts +8 -0
- package/dist/server/server/agent/providers/opencode/v2/configuration.js +64 -0
- package/dist/server/server/agent/providers/opencode/v2/history.d.ts +4 -0
- package/dist/server/server/agent/providers/opencode/v2/history.js +15 -0
- package/dist/server/server/agent/providers/opencode/v2/mapping.d.ts +7 -0
- package/dist/server/server/agent/providers/opencode/v2/mapping.js +42 -0
- package/dist/server/server/agent/providers/opencode/v2/permissions.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/v2/permissions.js +157 -0
- package/dist/server/server/agent/providers/opencode/v2/readiness.d.ts +18 -0
- package/dist/server/server/agent/providers/opencode/v2/readiness.js +51 -0
- package/dist/server/server/agent/providers/opencode/v2/runtime.d.ts +36 -0
- package/dist/server/server/agent/providers/opencode/v2/runtime.js +230 -0
- package/dist/server/server/agent/providers/opencode/v2/session.d.ts +84 -0
- package/dist/server/server/agent/providers/opencode/v2/session.js +465 -0
- package/dist/server/server/agent/providers/opencode/v2/structured-output.d.ts +8 -0
- package/dist/server/server/agent/providers/opencode/v2/structured-output.js +32 -0
- package/dist/server/server/agent/providers/opencode/v2/timeline.d.ts +28 -0
- package/dist/server/server/agent/providers/opencode/v2/timeline.js +154 -0
- package/dist/server/server/agent/providers/opencode/v2/turns.d.ts +40 -0
- package/dist/server/server/agent/providers/opencode/v2/turns.js +226 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +26 -7
- package/dist/server/server/agent/providers/opencode-agent.js +100 -40
- package/dist/server/server/agent/providers/pi/agent.d.ts +6 -6
- package/dist/server/server/agent/providers/pi/agent.js +114 -165
- package/dist/server/server/agent/providers/pi/extensions/child-session.d.ts +4 -0
- package/dist/server/server/agent/providers/pi/extensions/child-session.js +59 -0
- package/dist/server/server/agent/providers/pi/extensions/contract.d.ts +68 -0
- package/dist/server/server/agent/providers/pi/extensions/contract.js +2 -0
- package/dist/server/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/gotgenes-pi-subagents/index.js +159 -0
- package/dist/server/server/agent/providers/pi/extensions/host.d.ts +28 -0
- package/dist/server/server/agent/providers/pi/extensions/host.js +94 -0
- package/dist/server/server/agent/providers/pi/extensions/index.d.ts +7 -0
- package/dist/server/server/agent/providers/pi/extensions/index.js +6 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-ask-user/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-ask-user/index.js +138 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-example-todo/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-example-todo/index.js +38 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-goal-x/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-goal-x/index.js +63 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-mcp-adapter/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-mcp-adapter/index.js +48 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-subagents/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/pi-subagents/index.js +131 -0
- package/dist/server/server/agent/providers/pi/extensions/registry.d.ts +2 -0
- package/dist/server/server/agent/providers/pi/extensions/registry.js +21 -0
- package/dist/server/server/agent/providers/pi/extensions/rpiv-ask-user-question/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/rpiv-ask-user-question/index.js +180 -0
- package/dist/server/server/agent/providers/pi/extensions/rpiv-todo/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/rpiv-todo/index.js +51 -0
- package/dist/server/server/agent/providers/pi/extensions/subagent-fixture-test.d.ts +22 -0
- package/dist/server/server/agent/providers/pi/extensions/subagent-fixture-test.js +66 -0
- package/dist/server/server/agent/providers/pi/extensions/task-fixture-test.d.ts +4 -0
- package/dist/server/server/agent/providers/pi/extensions/task-fixture-test.js +79 -0
- package/dist/server/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/extensions/tintinweb-pi-subagents/index.js +149 -0
- package/dist/server/server/agent/providers/pi/history-mapper.d.ts +7 -2
- package/dist/server/server/agent/providers/pi/history-mapper.js +78 -35
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +170 -0
- package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +355 -0
- package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +1 -6
- package/dist/server/server/agent/providers/pi/tool-call-mapper.js +1 -97
- package/dist/server/server/agent/tools/paseo-tools.js +22 -15
- package/dist/server/server/auth.d.ts +1 -2
- package/dist/server/server/auth.js +8 -14
- package/dist/server/server/bootstrap.js +10 -2
- package/dist/server/server/daemon-instance.d.ts +1 -0
- package/dist/server/server/daemon-instance.js +1 -0
- package/dist/server/server/exports.d.ts +1 -1
- package/dist/server/server/exports.js +1 -1
- package/dist/server/server/local-credential.d.ts +6 -0
- package/dist/server/server/local-credential.js +82 -0
- package/dist/server/server/session-admission-auth.d.ts +16 -0
- package/dist/server/server/session-admission-auth.js +26 -0
- package/dist/server/server/websocket-server.d.ts +6 -1
- package/dist/server/server/websocket-server.js +132 -55
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-f41bce08c3c4ac2de0652d20234d19e9.js → desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js} +1 -1
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-f41bce08c3c4ac2de0652d20234d19e9.js.br → desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-9da07adb97f5e0eb726f7762afee43f5.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-9e271e161480b0d1dd35e58616a37341.js → desktop-attachment-store-d5d3402a752965d3037191484127123b.js} +1 -1
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-9e271e161480b0d1dd35e58616a37341.js.br → desktop-attachment-store-d5d3402a752965d3037191484127123b.js.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-d5d3402a752965d3037191484127123b.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-8868523fc6d311c5da524c067d1cd426.js → index-69ce3c243032c3bedfc6f2ad379a4cdd.js} +1579 -1575
- package/dist/server/web-ui/_expo/static/js/web/index-69ce3c243032c3bedfc6f2ad379a4cdd.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-69ce3c243032c3bedfc6f2ad379a4cdd.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-369cdb67f5856fd2c72d640df0b7b79e.js → indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js} +1 -1
- package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-369cdb67f5856fd2c72d640df0b7b79e.js.br → indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-3afc19e43c8152bd3c6d71b3ea8776dc.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-bcd89f6ce714279a8f4fa0e024f3a610.js → native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-187a9d5c32188f357dbbf037a0f89073.js.gz +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/dist/src/server/daemon-instance.js +1 -0
- package/dist/src/server/local-credential.js +82 -0
- package/package.json +8 -7
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-f41bce08c3c4ac2de0652d20234d19e9.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-9e271e161480b0d1dd35e58616a37341.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-8868523fc6d311c5da524c067d1cd426.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-8868523fc6d311c5da524c067d1cd426.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-369cdb67f5856fd2c72d640df0b7b79e.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-bcd89f6ce714279a8f4fa0e024f3a610.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-bcd89f6ce714279a8f4fa0e024f3a610.js.gz +0 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { SessionChildren } from "./children.js";
|
|
2
|
+
import { SessionTurns } from "./turns.js";
|
|
3
|
+
import { V2Timeline } from "./timeline.js";
|
|
4
|
+
import { waitForLocationReady, awaitPaseoPlugin } from "./readiness.js";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
import { toDiagnosticErrorMessage } from "../../diagnostic-utils.js";
|
|
7
|
+
import { runProviderTurn } from "../../provider-runner.js";
|
|
8
|
+
import { composeSystemPromptParts } from "../../../system-prompt.js";
|
|
9
|
+
import { raceProviderRefreshAbort } from "../../../provider-refresh-deadline.js";
|
|
10
|
+
import { modelRef, modesFromV2 } from "./mapping.js";
|
|
11
|
+
import { V2_CAPABILITIES } from "./capabilities.js";
|
|
12
|
+
import { features } from "./configuration.js";
|
|
13
|
+
import { commands } from "./commands.js";
|
|
14
|
+
import { messages } from "./history.js";
|
|
15
|
+
import { SessionPermissions } from "./permissions.js";
|
|
16
|
+
export class OpenCodeV2Session {
|
|
17
|
+
constructor(connection, info, config, logger, persist, requiresPaseoPlugin, unbind, bindChild, acquire, moved) {
|
|
18
|
+
this.connection = connection;
|
|
19
|
+
this.info = info;
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.logger = logger;
|
|
22
|
+
this.persist = persist;
|
|
23
|
+
this.requiresPaseoPlugin = requiresPaseoPlugin;
|
|
24
|
+
this.unbind = unbind;
|
|
25
|
+
this.acquire = acquire;
|
|
26
|
+
this.moved = moved;
|
|
27
|
+
this.provider = "opencode";
|
|
28
|
+
this.capabilities = V2_CAPABILITIES;
|
|
29
|
+
this.listeners = new Set();
|
|
30
|
+
this.timeline = new V2Timeline();
|
|
31
|
+
this.abort = new AbortController();
|
|
32
|
+
this.stream = null;
|
|
33
|
+
this.streamAbort = new AbortController();
|
|
34
|
+
this.exited = false;
|
|
35
|
+
this.reconnecting = null;
|
|
36
|
+
this.sync = Promise.resolve();
|
|
37
|
+
this.dirty = false;
|
|
38
|
+
this.refreshTimer = null;
|
|
39
|
+
this.closed = false;
|
|
40
|
+
this.history = [];
|
|
41
|
+
this.modes = [];
|
|
42
|
+
this.permissions = new SessionPermissions(() => this.client, info.id, config, (event) => this.emit(event));
|
|
43
|
+
this.children = new SessionChildren({
|
|
44
|
+
client: () => this.client,
|
|
45
|
+
id: info.id,
|
|
46
|
+
emit: (event) => this.emit(event),
|
|
47
|
+
reconcilePermissions: (id) => this.permissions.reconcile(id),
|
|
48
|
+
bindChild,
|
|
49
|
+
});
|
|
50
|
+
this.turns = new SessionTurns({
|
|
51
|
+
client: () => this.client,
|
|
52
|
+
id: info.id,
|
|
53
|
+
cwd: config.cwd,
|
|
54
|
+
signal: this.abort.signal,
|
|
55
|
+
emit: (event) => this.emit(event),
|
|
56
|
+
reconcile: async () => {
|
|
57
|
+
await this.reconcile();
|
|
58
|
+
return { info: this.info, history: this.history };
|
|
59
|
+
},
|
|
60
|
+
clearPermissions: async () => {
|
|
61
|
+
for (const request of this.permissions.list())
|
|
62
|
+
await this.permissions.respondToPermission(request.id, { behavior: "deny" });
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
get id() {
|
|
67
|
+
return this.info.id;
|
|
68
|
+
}
|
|
69
|
+
get features() {
|
|
70
|
+
return features(this.config);
|
|
71
|
+
}
|
|
72
|
+
get client() {
|
|
73
|
+
return this.connection.client;
|
|
74
|
+
}
|
|
75
|
+
async initialize(launch) {
|
|
76
|
+
this.launchEnv = launch?.env;
|
|
77
|
+
this.watchExit(this.connection);
|
|
78
|
+
await this.configureConnection();
|
|
79
|
+
const location = { directory: this.config.cwd };
|
|
80
|
+
this.modes = modesFromV2((await this.client.agent.list({ location })).data);
|
|
81
|
+
this.timeline.messages(await messages(this.client, this.id));
|
|
82
|
+
await this.startStream();
|
|
83
|
+
await this.children.reconcile(this.id);
|
|
84
|
+
}
|
|
85
|
+
watchExit(connection) {
|
|
86
|
+
void connection.exited.then((error) => {
|
|
87
|
+
if (this.closed || this.connection !== connection)
|
|
88
|
+
return undefined;
|
|
89
|
+
this.exited = true;
|
|
90
|
+
this.streamAbort.abort(error);
|
|
91
|
+
this.turns.fail(error);
|
|
92
|
+
return undefined;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async configureConnection() {
|
|
96
|
+
const location = { directory: this.config.cwd };
|
|
97
|
+
await waitForLocationReady({ client: this.client, location, signal: this.abort.signal });
|
|
98
|
+
if (this.requiresPaseoPlugin)
|
|
99
|
+
await awaitPaseoPlugin({ client: this.client, location, signal: this.abort.signal });
|
|
100
|
+
if (this.launchEnv)
|
|
101
|
+
await this.client.session.environment({ sessionID: this.id, variables: this.launchEnv });
|
|
102
|
+
for (const [server, config] of Object.entries(this.config.mcpServers ?? {})) {
|
|
103
|
+
await this.client.mcp.add({
|
|
104
|
+
server,
|
|
105
|
+
location,
|
|
106
|
+
config: config.type === "stdio"
|
|
107
|
+
? {
|
|
108
|
+
type: "local",
|
|
109
|
+
command: [config.command, ...(config.args ?? [])],
|
|
110
|
+
environment: config.env,
|
|
111
|
+
codemode: false,
|
|
112
|
+
}
|
|
113
|
+
: {
|
|
114
|
+
type: "remote",
|
|
115
|
+
url: config.url,
|
|
116
|
+
headers: config.headers,
|
|
117
|
+
oauth: false,
|
|
118
|
+
codemode: false,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
await this.awaitMcp(server);
|
|
122
|
+
}
|
|
123
|
+
const system = composeSystemPromptParts(this.config.systemPrompt, this.config.daemonAppendSystemPrompt);
|
|
124
|
+
if (system)
|
|
125
|
+
await this.client.session.instructions.entry.put({
|
|
126
|
+
sessionID: this.id,
|
|
127
|
+
key: "paseo",
|
|
128
|
+
value: system,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async startStream() {
|
|
132
|
+
let ready;
|
|
133
|
+
let fail;
|
|
134
|
+
const first = new Promise((resolve, reject) => {
|
|
135
|
+
ready = resolve;
|
|
136
|
+
fail = reject;
|
|
137
|
+
});
|
|
138
|
+
this.stream = this.consume(ready, fail);
|
|
139
|
+
await raceProviderRefreshAbort(AbortSignal.any([this.abort.signal, AbortSignal.timeout(30000)]), first);
|
|
140
|
+
}
|
|
141
|
+
async reconnectIfExited() {
|
|
142
|
+
if (this.closed)
|
|
143
|
+
throw new Error("OpenCode session is closed");
|
|
144
|
+
if (!this.exited)
|
|
145
|
+
return;
|
|
146
|
+
this.reconnecting ?? (this.reconnecting = this.reconnect().finally(() => {
|
|
147
|
+
this.reconnecting = null;
|
|
148
|
+
}));
|
|
149
|
+
await this.reconnecting;
|
|
150
|
+
}
|
|
151
|
+
async reconnect() {
|
|
152
|
+
if (!this.acquire)
|
|
153
|
+
throw new Error("OpenCode helper server exited");
|
|
154
|
+
await this.stream;
|
|
155
|
+
await this.sync.catch(() => undefined);
|
|
156
|
+
const next = await this.acquire();
|
|
157
|
+
if (this.closed) {
|
|
158
|
+
await next.release();
|
|
159
|
+
throw new Error("OpenCode session is closed");
|
|
160
|
+
}
|
|
161
|
+
const old = this.connection;
|
|
162
|
+
this.connection = next;
|
|
163
|
+
this.streamAbort = new AbortController();
|
|
164
|
+
this.exited = false;
|
|
165
|
+
this.watchExit(next);
|
|
166
|
+
try {
|
|
167
|
+
await this.configureConnection();
|
|
168
|
+
await this.startStream();
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.connection = old;
|
|
172
|
+
this.exited = true;
|
|
173
|
+
this.streamAbort.abort();
|
|
174
|
+
await next.release();
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
this.moved?.(next);
|
|
178
|
+
await old.release();
|
|
179
|
+
}
|
|
180
|
+
subscribe(callback) {
|
|
181
|
+
this.listeners.add(callback);
|
|
182
|
+
return () => {
|
|
183
|
+
this.listeners.delete(callback);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async awaitMcp(server) {
|
|
187
|
+
const signal = AbortSignal.any([this.abort.signal, AbortSignal.timeout(30000)]);
|
|
188
|
+
while (true) {
|
|
189
|
+
const catalog = await this.client.mcp.list({ location: { directory: this.config.cwd } }, { signal });
|
|
190
|
+
const entry = catalog.data.find((item) => item.name === server);
|
|
191
|
+
if (entry?.status.status === "connected")
|
|
192
|
+
return;
|
|
193
|
+
if (entry && entry.status.status !== "pending") {
|
|
194
|
+
const reason = entry.status.status === "failed" ? entry.status.error : entry.status.status;
|
|
195
|
+
throw new Error(`OpenCode MCP server ${server} failed to connect: ${reason}`);
|
|
196
|
+
}
|
|
197
|
+
await delay(50, undefined, { signal });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
emit(event) {
|
|
201
|
+
for (const listener of this.listeners)
|
|
202
|
+
listener(event);
|
|
203
|
+
}
|
|
204
|
+
emitTimeline(event) {
|
|
205
|
+
this.emit({ ...event, ...(this.turns.id ? { turnId: this.turns.id } : {}) });
|
|
206
|
+
}
|
|
207
|
+
async *streamHistory() {
|
|
208
|
+
await this.reconnectIfExited();
|
|
209
|
+
const history = new V2Timeline(false);
|
|
210
|
+
yield* history.messages(await messages(this.client, this.id));
|
|
211
|
+
}
|
|
212
|
+
async getRuntimeInfo() {
|
|
213
|
+
await this.reconnectIfExited();
|
|
214
|
+
this.info = await this.client.session.get({ sessionID: this.id });
|
|
215
|
+
return {
|
|
216
|
+
provider: "opencode",
|
|
217
|
+
sessionId: this.id,
|
|
218
|
+
model: this.info.model ? `${this.info.model.providerID}/${this.info.model.id}` : null,
|
|
219
|
+
modeId: this.info.agent ?? null,
|
|
220
|
+
thinkingOptionId: this.info.model?.variant ?? null,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async getAvailableModes() {
|
|
224
|
+
return this.modes;
|
|
225
|
+
}
|
|
226
|
+
async getCurrentMode() {
|
|
227
|
+
return this.info.agent ?? null;
|
|
228
|
+
}
|
|
229
|
+
async setMode(modeId) {
|
|
230
|
+
await this.reconnectIfExited();
|
|
231
|
+
await this.client.session.switchAgent({ sessionID: this.id, agent: modeId });
|
|
232
|
+
this.info.agent = modeId;
|
|
233
|
+
this.config.modeId = modeId;
|
|
234
|
+
this.emit({
|
|
235
|
+
type: "mode_changed",
|
|
236
|
+
provider: "opencode",
|
|
237
|
+
currentModeId: modeId,
|
|
238
|
+
availableModes: this.modes,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async setModel(model) {
|
|
242
|
+
await this.reconnectIfExited();
|
|
243
|
+
const selected = model
|
|
244
|
+
? modelRef(model, this.config.thinkingOptionId)
|
|
245
|
+
: (await this.client.model.default({ location: { directory: this.config.cwd } })).data;
|
|
246
|
+
if (!selected)
|
|
247
|
+
throw new Error("OpenCode has no default model");
|
|
248
|
+
await this.client.session.switchModel({
|
|
249
|
+
sessionID: this.id,
|
|
250
|
+
model: {
|
|
251
|
+
id: selected.id,
|
|
252
|
+
providerID: selected.providerID,
|
|
253
|
+
variant: this.config.thinkingOptionId,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
this.config.model = model ?? undefined;
|
|
257
|
+
this.emit({
|
|
258
|
+
type: "model_changed",
|
|
259
|
+
provider: "opencode",
|
|
260
|
+
runtimeInfo: await this.getRuntimeInfo(),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async setThinkingOption(variant) {
|
|
264
|
+
await this.reconnectIfExited();
|
|
265
|
+
const model = this.info.model;
|
|
266
|
+
if (!model)
|
|
267
|
+
throw new Error("Select an OpenCode model before changing its variant");
|
|
268
|
+
await this.client.session.switchModel({
|
|
269
|
+
sessionID: this.id,
|
|
270
|
+
model: { id: model.id, providerID: model.providerID, ...(variant ? { variant } : {}) },
|
|
271
|
+
});
|
|
272
|
+
this.config.thinkingOptionId = variant ?? undefined;
|
|
273
|
+
this.info = await this.client.session.get({ sessionID: this.id });
|
|
274
|
+
this.emit({ type: "thinking_option_changed", provider: "opencode", thinkingOptionId: variant });
|
|
275
|
+
}
|
|
276
|
+
async setFeature(id, value) {
|
|
277
|
+
if (id !== "auto_accept" || typeof value !== "boolean")
|
|
278
|
+
throw new Error("Unknown OpenCode feature");
|
|
279
|
+
this.config.featureValues = { ...this.config.featureValues, [id]: value };
|
|
280
|
+
if (value && !this.config.toolPolicy)
|
|
281
|
+
for (const request of this.permissions.list())
|
|
282
|
+
if (request.kind !== "question")
|
|
283
|
+
await this.respondToPermission(request.id, { behavior: "allow" });
|
|
284
|
+
}
|
|
285
|
+
async listCommands() {
|
|
286
|
+
await this.reconnectIfExited();
|
|
287
|
+
return commands(this.client, this.config.cwd);
|
|
288
|
+
}
|
|
289
|
+
describePersistence() {
|
|
290
|
+
return {
|
|
291
|
+
provider: "opencode",
|
|
292
|
+
sessionId: this.id,
|
|
293
|
+
nativeHandle: this.id,
|
|
294
|
+
metadata: { ...this.config },
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
run(prompt, options) {
|
|
298
|
+
return runProviderTurn({
|
|
299
|
+
prompt,
|
|
300
|
+
runOptions: options,
|
|
301
|
+
startTurn: this.startTurn.bind(this),
|
|
302
|
+
subscribe: this.subscribe.bind(this),
|
|
303
|
+
getSessionId: () => this.id,
|
|
304
|
+
reduceFinalText: ({ current, item }) => item.type === "assistant_message" ? current + item.text : current,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
async startTurn(prompt, options) {
|
|
308
|
+
await this.reconnectIfExited();
|
|
309
|
+
return this.turns.startTurn(prompt, options);
|
|
310
|
+
}
|
|
311
|
+
steerActiveTurn(prompt, options) {
|
|
312
|
+
return this.turns.steerActiveTurn(prompt, options);
|
|
313
|
+
}
|
|
314
|
+
interrupt() {
|
|
315
|
+
return this.turns.interrupt();
|
|
316
|
+
}
|
|
317
|
+
async revertBoth(input) {
|
|
318
|
+
await this.interrupt();
|
|
319
|
+
await this.reconnectIfExited();
|
|
320
|
+
await this.client.session.revert.stage({
|
|
321
|
+
sessionID: this.id,
|
|
322
|
+
messageID: input.messageId,
|
|
323
|
+
files: true,
|
|
324
|
+
});
|
|
325
|
+
await this.client.session.revert.commit({ sessionID: this.id });
|
|
326
|
+
}
|
|
327
|
+
getPendingPermissions() {
|
|
328
|
+
return [...this.permissions.list()];
|
|
329
|
+
}
|
|
330
|
+
async respondToPermission(requestId, response) {
|
|
331
|
+
await this.reconnectIfExited();
|
|
332
|
+
await this.permissions.respondToPermission(requestId, response);
|
|
333
|
+
}
|
|
334
|
+
async reconcileSnapshot() {
|
|
335
|
+
const [info, history] = await Promise.all([
|
|
336
|
+
this.client.session.get({ sessionID: this.id }),
|
|
337
|
+
messages(this.client, this.id),
|
|
338
|
+
]);
|
|
339
|
+
this.info = info;
|
|
340
|
+
this.history = history;
|
|
341
|
+
for (const event of this.timeline.messages(history))
|
|
342
|
+
this.emitTimeline(event);
|
|
343
|
+
await this.permissions.reconcile(this.id);
|
|
344
|
+
}
|
|
345
|
+
async reconcile() {
|
|
346
|
+
this.dirty = true;
|
|
347
|
+
this.sync = this.sync.catch(() => undefined).then(() => this.drainReconciliation());
|
|
348
|
+
return this.sync;
|
|
349
|
+
}
|
|
350
|
+
async drainReconciliation() {
|
|
351
|
+
while (this.dirty && !this.closed) {
|
|
352
|
+
this.dirty = false;
|
|
353
|
+
await this.reconcileSnapshot();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async reconcileConnection() {
|
|
357
|
+
await this.reconcile();
|
|
358
|
+
await this.children.reconcile(this.id);
|
|
359
|
+
const active = await this.client.session.active();
|
|
360
|
+
if (active[this.id])
|
|
361
|
+
this.turns.observeActiveTurn();
|
|
362
|
+
}
|
|
363
|
+
scheduleReconcile() {
|
|
364
|
+
if (this.refreshTimer || this.closed)
|
|
365
|
+
return;
|
|
366
|
+
this.refreshTimer = setTimeout(() => {
|
|
367
|
+
this.refreshTimer = null;
|
|
368
|
+
void this.reconcile().catch((error) => this.logger.warn({ error: toDiagnosticErrorMessage(error) }, "OpenCode reconciliation failed"));
|
|
369
|
+
}, 25);
|
|
370
|
+
}
|
|
371
|
+
observeOwnEvent(event) {
|
|
372
|
+
if (event.type === "form.created" && event.data.form.sessionID === this.id) {
|
|
373
|
+
this.scheduleReconcile();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (!("sessionID" in event.data) || event.data.sessionID !== this.id)
|
|
377
|
+
return;
|
|
378
|
+
if (event.type === "session.text.started" || event.type === "session.reasoning.started") {
|
|
379
|
+
if (event.type === "session.text.started" && this.turns.hasStructuredOutput)
|
|
380
|
+
return;
|
|
381
|
+
this.timeline.startPart({
|
|
382
|
+
...event.data,
|
|
383
|
+
type: event.type === "session.text.started" ? "text" : "reasoning",
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (event.type === "session.text.delta" || event.type === "session.reasoning.delta") {
|
|
388
|
+
const isText = event.type === "session.text.delta";
|
|
389
|
+
// A structured-output turn supplies its answer through a tool, so streamed
|
|
390
|
+
// prose is not part of the result and must not reach the timeline.
|
|
391
|
+
if (isText && this.turns.hasStructuredOutput)
|
|
392
|
+
return;
|
|
393
|
+
const streamed = this.timeline.delta({
|
|
394
|
+
assistantMessageID: event.data.assistantMessageID,
|
|
395
|
+
ordinal: event.data.ordinal,
|
|
396
|
+
delta: event.data.delta,
|
|
397
|
+
type: isText ? "text" : "reasoning",
|
|
398
|
+
});
|
|
399
|
+
if (streamed)
|
|
400
|
+
this.emitTimeline(streamed);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
this.permissions.observe(event);
|
|
404
|
+
if (event.type === "session.execution.failed")
|
|
405
|
+
this.turns.executionError = event.data.error.message;
|
|
406
|
+
this.scheduleReconcile();
|
|
407
|
+
if (event.type !== "session.execution.started" || this.turns.id)
|
|
408
|
+
return;
|
|
409
|
+
this.turns.observeActiveTurn();
|
|
410
|
+
}
|
|
411
|
+
async consume(ready, fail) {
|
|
412
|
+
const signal = AbortSignal.any([this.abort.signal, this.streamAbort.signal]);
|
|
413
|
+
let connected = false;
|
|
414
|
+
while (!this.closed && !signal.aborted) {
|
|
415
|
+
try {
|
|
416
|
+
for await (const event of this.client.event.subscribe({ signal })) {
|
|
417
|
+
if (this.closed || signal.aborted)
|
|
418
|
+
return;
|
|
419
|
+
if (event.type === "server.connected") {
|
|
420
|
+
this.timeline.resetStreams();
|
|
421
|
+
await this.reconcileConnection();
|
|
422
|
+
connected = true;
|
|
423
|
+
ready();
|
|
424
|
+
}
|
|
425
|
+
await this.children.observe(event);
|
|
426
|
+
this.observeOwnEvent(event);
|
|
427
|
+
}
|
|
428
|
+
if (!connected)
|
|
429
|
+
throw new Error("OpenCode event stream ended before connecting");
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (this.closed || signal.aborted)
|
|
433
|
+
return;
|
|
434
|
+
if (!connected) {
|
|
435
|
+
fail(error);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
this.logger.warn({ error: toDiagnosticErrorMessage(error) }, "OpenCode event stream interrupted; reconciling on reconnect");
|
|
439
|
+
}
|
|
440
|
+
await delay(100, undefined, { signal }).catch(() => undefined);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async close() {
|
|
444
|
+
if (this.closed)
|
|
445
|
+
return;
|
|
446
|
+
this.closed = true;
|
|
447
|
+
this.abort.abort();
|
|
448
|
+
this.streamAbort.abort();
|
|
449
|
+
if (this.refreshTimer)
|
|
450
|
+
clearTimeout(this.refreshTimer);
|
|
451
|
+
await this.stream;
|
|
452
|
+
await this.reconnecting?.catch(() => undefined);
|
|
453
|
+
await this.sync.catch(() => undefined);
|
|
454
|
+
this.unbind?.();
|
|
455
|
+
try {
|
|
456
|
+
if (!this.persist)
|
|
457
|
+
await this.client.session.remove({ sessionID: this.id });
|
|
458
|
+
}
|
|
459
|
+
finally {
|
|
460
|
+
await this.connection.release();
|
|
461
|
+
}
|
|
462
|
+
this.listeners.clear();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { SessionMessageInfo } from "@opencode/client";
|
|
3
|
+
export declare const STRUCTURED_OUTPUT_TOOL = "paseo_structured_output";
|
|
4
|
+
export declare function structuredOutput(input: unknown): {
|
|
5
|
+
schema: boolean | Record<string, z.core.util.JSONType>;
|
|
6
|
+
assert(history: SessionMessageInfo[]): void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=structured-output.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Ajv } from "ajv";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const STRUCTURED_OUTPUT_TOOL = "paseo_structured_output";
|
|
4
|
+
const schemas = new Ajv({ strict: false, allErrors: true });
|
|
5
|
+
export function structuredOutput(input) {
|
|
6
|
+
const schema = z.union([z.boolean(), z.record(z.string(), z.json())]).parse(input);
|
|
7
|
+
const validate = schemas.compile(schema);
|
|
8
|
+
return {
|
|
9
|
+
schema,
|
|
10
|
+
assert(history) {
|
|
11
|
+
const start = history.findLastIndex((message) => message.type === "user");
|
|
12
|
+
for (const message of history.slice(start + 1)) {
|
|
13
|
+
if (message.type !== "assistant")
|
|
14
|
+
continue;
|
|
15
|
+
for (const part of message.content) {
|
|
16
|
+
if (part.type !== "tool" ||
|
|
17
|
+
part.name !== STRUCTURED_OUTPUT_TOOL ||
|
|
18
|
+
part.state.status !== "completed")
|
|
19
|
+
continue;
|
|
20
|
+
const result = part.state.metadata?.paseoStructuredOutput;
|
|
21
|
+
if (result === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
if (!validate(result))
|
|
24
|
+
throw new Error(`Invalid OpenCode structured output: ${schemas.errorsText(validate.errors)}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw new Error("OpenCode finished without submitting the required structured output");
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=structured-output.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SessionMessageInfo } from "@opencode/client";
|
|
2
|
+
import type { AgentStreamEvent } from "../../../agent-sdk-types.js";
|
|
3
|
+
export declare class V2Timeline {
|
|
4
|
+
private readonly includeClientMessageId;
|
|
5
|
+
private readonly content;
|
|
6
|
+
private readonly streams;
|
|
7
|
+
resetStreams(): void;
|
|
8
|
+
startPart(part: TextPartIdentity): void;
|
|
9
|
+
constructor(includeClientMessageId?: boolean);
|
|
10
|
+
delta(event: TextPartDelta): AgentStreamEvent | null;
|
|
11
|
+
messages(messages: SessionMessageInfo[]): AgentStreamEvent[];
|
|
12
|
+
private userMessage;
|
|
13
|
+
private assistantMessage;
|
|
14
|
+
private structuredOutputPart;
|
|
15
|
+
private textPart;
|
|
16
|
+
private toolPart;
|
|
17
|
+
private compactionMessage;
|
|
18
|
+
}
|
|
19
|
+
interface TextPartIdentity {
|
|
20
|
+
assistantMessageID: string;
|
|
21
|
+
type: "text" | "reasoning";
|
|
22
|
+
ordinal: number;
|
|
23
|
+
}
|
|
24
|
+
interface TextPartDelta extends TextPartIdentity {
|
|
25
|
+
delta: string;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=timeline.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { mapOpencodeToolCall } from "../tool-call-mapper.js";
|
|
2
|
+
import { STRUCTURED_OUTPUT_TOOL } from "./structured-output.js";
|
|
3
|
+
function textPartKey(messageID, part, ordinal) {
|
|
4
|
+
return `${messageID}:${part}:${ordinal}`;
|
|
5
|
+
}
|
|
6
|
+
// Text and reasoning each number parts from zero, independent of snapshot position.
|
|
7
|
+
export class V2Timeline {
|
|
8
|
+
resetStreams() {
|
|
9
|
+
this.streams.clear();
|
|
10
|
+
}
|
|
11
|
+
startPart(part) {
|
|
12
|
+
this.streams.set(textPartKey(part.assistantMessageID, part.type, part.ordinal), "");
|
|
13
|
+
}
|
|
14
|
+
constructor(includeClientMessageId = true) {
|
|
15
|
+
this.includeClientMessageId = includeClientMessageId;
|
|
16
|
+
this.content = new Map();
|
|
17
|
+
this.streams = new Map();
|
|
18
|
+
}
|
|
19
|
+
delta(event) {
|
|
20
|
+
const key = textPartKey(event.assistantMessageID, event.type, event.ordinal);
|
|
21
|
+
const streamed = this.streams.get(key);
|
|
22
|
+
// Deltas are ephemeral and have no offset. After reconnect, only stream
|
|
23
|
+
// parts whose start we observed; snapshots recover any interrupted part.
|
|
24
|
+
if (streamed === undefined || event.delta.length === 0)
|
|
25
|
+
return null;
|
|
26
|
+
const text = streamed + event.delta;
|
|
27
|
+
this.streams.set(key, text);
|
|
28
|
+
const emitted = this.content.get(key) ?? "";
|
|
29
|
+
if (text.length <= emitted.length)
|
|
30
|
+
return null;
|
|
31
|
+
if (!text.startsWith(emitted))
|
|
32
|
+
throw new Error("OpenCode changed previously emitted message content");
|
|
33
|
+
this.content.set(key, text);
|
|
34
|
+
const suffix = text.slice(emitted.length);
|
|
35
|
+
const item = event.type === "text"
|
|
36
|
+
? { type: "assistant_message", text: suffix, messageId: event.assistantMessageID }
|
|
37
|
+
: { type: "reasoning", text: suffix };
|
|
38
|
+
return { type: "timeline", provider: "opencode", item, timestamp: new Date().toISOString() };
|
|
39
|
+
}
|
|
40
|
+
messages(messages) {
|
|
41
|
+
const events = [];
|
|
42
|
+
const state = { structured: false, accepted: false };
|
|
43
|
+
for (const message of messages) {
|
|
44
|
+
const timestamp = new Date(message.time.created).toISOString();
|
|
45
|
+
const push = (item) => events.push({ type: "timeline", provider: "opencode", item, timestamp });
|
|
46
|
+
if (message.type === "user")
|
|
47
|
+
this.userMessage(message, state, push);
|
|
48
|
+
else if (message.type === "assistant")
|
|
49
|
+
this.assistantMessage(message, state, push);
|
|
50
|
+
else if (message.type === "compaction")
|
|
51
|
+
this.compactionMessage(message, push);
|
|
52
|
+
}
|
|
53
|
+
return events;
|
|
54
|
+
}
|
|
55
|
+
userMessage(message, state, push) {
|
|
56
|
+
state.structured = message.metadata?.paseoOutputSchema !== undefined;
|
|
57
|
+
state.accepted = false;
|
|
58
|
+
if (this.content.has(message.id))
|
|
59
|
+
return;
|
|
60
|
+
this.content.set(message.id, message.text);
|
|
61
|
+
const clientMessageId = message.metadata?.paseoClientMessageId;
|
|
62
|
+
push({
|
|
63
|
+
type: "user_message",
|
|
64
|
+
text: message.text,
|
|
65
|
+
messageId: message.id,
|
|
66
|
+
...(this.includeClientMessageId && typeof clientMessageId === "string"
|
|
67
|
+
? { clientMessageId }
|
|
68
|
+
: {}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
assistantMessage(message, state, push) {
|
|
72
|
+
// Snapshot parts are indexed by position in `message.content`, but deltas
|
|
73
|
+
// number text and reasoning separately. Track a per-type ordinal so both
|
|
74
|
+
// paths address the same cursor entry.
|
|
75
|
+
let textOrdinal = 0;
|
|
76
|
+
let reasoningOrdinal = 0;
|
|
77
|
+
message.content.forEach((part, partIndex) => {
|
|
78
|
+
if (part.type === "tool" && part.name === STRUCTURED_OUTPUT_TOOL) {
|
|
79
|
+
this.structuredOutputPart(message.id, partIndex, part, state, push);
|
|
80
|
+
}
|
|
81
|
+
else if (part.type === "text" || part.type === "reasoning") {
|
|
82
|
+
const ordinal = part.type === "text" ? textOrdinal++ : reasoningOrdinal++;
|
|
83
|
+
this.textPart(message.id, part, ordinal, state, push);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.toolPart(message.id, partIndex, part, push);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
structuredOutputPart(messageID, partIndex, part, state, push) {
|
|
91
|
+
if (!state.structured || state.accepted || part.state.status !== "completed")
|
|
92
|
+
return;
|
|
93
|
+
const value = part.state.metadata?.paseoStructuredOutput;
|
|
94
|
+
if (value === undefined)
|
|
95
|
+
return;
|
|
96
|
+
state.accepted = true;
|
|
97
|
+
const key = `${messageID}:${partIndex}`;
|
|
98
|
+
const text = JSON.stringify(value);
|
|
99
|
+
if (this.content.get(key) === text)
|
|
100
|
+
return;
|
|
101
|
+
this.content.set(key, text);
|
|
102
|
+
push({ type: "assistant_message", text, messageId: messageID });
|
|
103
|
+
}
|
|
104
|
+
textPart(messageID, part, ordinal, state, push) {
|
|
105
|
+
if (state.structured && part.type === "text")
|
|
106
|
+
return;
|
|
107
|
+
const key = textPartKey(messageID, part.type, ordinal);
|
|
108
|
+
const previous = this.content.get(key) ?? "";
|
|
109
|
+
// Upstream snapshots may briefly lag the volatile delta stream.
|
|
110
|
+
if (part.text.length <= previous.length)
|
|
111
|
+
return;
|
|
112
|
+
if (!part.text.startsWith(previous))
|
|
113
|
+
throw new Error("OpenCode changed previously emitted message content");
|
|
114
|
+
this.content.set(key, part.text);
|
|
115
|
+
const suffix = part.text.slice(previous.length);
|
|
116
|
+
if (part.type === "text")
|
|
117
|
+
push({ type: "assistant_message", text: suffix, messageId: messageID });
|
|
118
|
+
else
|
|
119
|
+
push({ type: "reasoning", text: suffix });
|
|
120
|
+
}
|
|
121
|
+
toolPart(messageID, partIndex, part, push) {
|
|
122
|
+
const key = `${messageID}:${partIndex}`;
|
|
123
|
+
const serialized = JSON.stringify(part);
|
|
124
|
+
if (serialized === this.content.get(key))
|
|
125
|
+
return;
|
|
126
|
+
this.content.set(key, serialized);
|
|
127
|
+
const item = toolFromV2(part);
|
|
128
|
+
if (item)
|
|
129
|
+
push(item);
|
|
130
|
+
}
|
|
131
|
+
compactionMessage(message, push) {
|
|
132
|
+
const status = message.status === "running" ? "loading" : "completed";
|
|
133
|
+
if (this.content.get(message.id) === status)
|
|
134
|
+
return;
|
|
135
|
+
this.content.set(message.id, status);
|
|
136
|
+
push({ type: "compaction", status });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function toolFromV2(tool) {
|
|
140
|
+
const state = tool.state;
|
|
141
|
+
const output = "content" in state
|
|
142
|
+
? state.content?.map((part) => (part.type === "text" ? part.text : part.uri)).join("\n")
|
|
143
|
+
: undefined;
|
|
144
|
+
return mapOpencodeToolCall({
|
|
145
|
+
toolName: tool.name,
|
|
146
|
+
callId: tool.id,
|
|
147
|
+
input: state.input,
|
|
148
|
+
status: state.status === "error" ? "failed" : state.status,
|
|
149
|
+
output,
|
|
150
|
+
error: state.status === "error" ? state.error : undefined,
|
|
151
|
+
metadata: "metadata" in state ? state.metadata : undefined,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=timeline.js.map
|