@ai-sdk/harness-opencode 1.0.113 → 1.0.115

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/index.js CHANGED
@@ -909,7 +909,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
909
909
  ]);
910
910
 
911
911
  // src/version.ts
912
- var VERSION = true ? "1.0.113" : "0.0.0-test";
912
+ var VERSION = true ? "1.0.115" : "0.0.0-test";
913
913
 
914
914
  // src/opencode-harness.ts
915
915
  var OPENCODE_CLIENT_APP = `ai-sdk/harness-opencode/${VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-opencode",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -26,8 +26,8 @@
26
26
  }
27
27
  },
28
28
  "dependencies": {
29
- "@ai-sdk/harness": "1.0.111",
30
- "@ai-sdk/provider-utils": "5.0.40",
29
+ "@ai-sdk/harness": "1.0.113",
30
+ "@ai-sdk/provider-utils": "5.0.42",
31
31
  "ws": "8.21.0"
32
32
  },
33
33
  "peerDependencies": {
@@ -3,6 +3,7 @@ import {
3
3
  emitLegacyPartDelta,
4
4
  emitLegacyTextPartUpdate,
5
5
  emitMissingFinalDelta,
6
+ emitOpenCodeStreamStart,
6
7
  openCodeMessageInfoFromValue,
7
8
  type TranslationState,
8
9
  } from './opencode-events';
@@ -60,6 +61,33 @@ export function createEmitStreamEvent({
60
61
  stripWorkDir: (file: string) => string;
61
62
  formatError: (error: unknown) => string;
62
63
  }): (event: OpenCodeEvent) => void {
64
+ const compactionMessages = new Set<string>();
65
+ let compactionTrigger: 'auto' | 'manual' = 'auto';
66
+ let compaction:
67
+ | { id: string; trigger: 'auto' | 'manual'; text: Map<string, string> }
68
+ | undefined;
69
+ const finishCompaction = (failed = false) => {
70
+ if (!compaction) return;
71
+ if (failed) {
72
+ emit({
73
+ type: 'raw',
74
+ rawValue: {
75
+ type: 'opencode.compaction',
76
+ messageId: compaction.id,
77
+ status: 'failed',
78
+ },
79
+ });
80
+ } else {
81
+ emit({
82
+ type: 'compaction',
83
+ trigger: compaction.trigger,
84
+ summary: [...compaction.text.values()].join(''),
85
+ harnessMetadata: { opencode: { messageId: compaction.id } },
86
+ });
87
+ }
88
+ compaction = undefined;
89
+ };
90
+
63
91
  return event => {
64
92
  const type = event.type;
65
93
  const props = event.properties ?? {};
@@ -70,10 +98,62 @@ export function createEmitStreamEvent({
70
98
  const id = stringValue(info.id);
71
99
  const role = stringValue(info.role);
72
100
  if (id && role) state.messageRoles.set(id, role);
101
+ if (id && info.summary === true) {
102
+ if (!compactionMessages.has(id)) {
103
+ compactionMessages.add(id);
104
+ compaction = { id, trigger: compactionTrigger, text: new Map() };
105
+ emitOpenCodeStreamStart({ info, state, emit });
106
+ emit({
107
+ type: 'raw',
108
+ rawValue: {
109
+ type: 'opencode.compaction',
110
+ messageId: id,
111
+ status: 'started',
112
+ },
113
+ });
114
+ }
115
+ if (info.error && compaction?.id === id) finishCompaction(true);
116
+ }
73
117
  }
74
118
  return;
75
119
  }
76
120
 
121
+ if (type === 'session.compacted') {
122
+ finishCompaction();
123
+ return;
124
+ }
125
+ if (type === 'message.part.updated' || type === 'message.part.delta') {
126
+ const part = asOpenCodeObject(props.part);
127
+ if (type === 'message.part.updated' && part?.type === 'compaction') {
128
+ compactionTrigger = part.auto === false ? 'manual' : 'auto';
129
+ return;
130
+ }
131
+ const messageId = stringValue(
132
+ type === 'message.part.updated' ? part?.messageID : props.messageID,
133
+ );
134
+ if (messageId && compactionMessages.has(messageId)) {
135
+ if (type === 'message.part.updated') {
136
+ if (messageId === compaction?.id && part?.type === 'text') {
137
+ const id = stringValue(part.id);
138
+ if (id && typeof part.text === 'string') {
139
+ compaction.text.set(id, part.text);
140
+ }
141
+ }
142
+ emitLegacyStepFinishPart({ part: props.part, state, emit });
143
+ } else if (messageId === compaction?.id && props.field === 'text') {
144
+ const id = stringValue(props.partID);
145
+ if (
146
+ id &&
147
+ compaction.text.has(id) &&
148
+ typeof props.delta === 'string'
149
+ ) {
150
+ compaction.text.set(id, compaction.text.get(id)! + props.delta);
151
+ }
152
+ }
153
+ return;
154
+ }
155
+ }
156
+
77
157
  if (type === 'message.part.delta') {
78
158
  emitLegacyPartDelta({ props, state, emit });
79
159
  return;
@@ -310,6 +390,7 @@ export function createEmitStreamEvent({
310
390
  return;
311
391
  }
312
392
  if (type === 'session.error' || type === 'session.next.step.failed') {
393
+ finishCompaction(true);
313
394
  const error = props.error ?? event;
314
395
  emitError({
315
396
  error,
@@ -6,6 +6,7 @@ import {
6
6
  import { randomUUID } from 'node:crypto';
7
7
  import path from 'node:path';
8
8
  import { argv, env as procEnv } from 'node:process';
9
+ import { isDeepStrictEqual } from 'node:util';
9
10
  import type { StartMessage } from '../opencode-bridge-protocol';
10
11
 
11
12
  import {
@@ -61,6 +62,7 @@ type RuntimeState = {
61
62
  client?: OpenCodeClient;
62
63
  sessionId?: string;
63
64
  relay?: ToolRelay;
65
+ openCodeConfig?: Record<string, unknown>;
64
66
  toolNames: Set<string>;
65
67
  mcpToolPrefixes: Set<string>;
66
68
  };
@@ -203,44 +205,68 @@ async function ensureRuntime({
203
205
  turn: BridgeTurn;
204
206
  emit: Emit;
205
207
  }): Promise<void> {
206
- if (runtime.client) return;
208
+ if (
209
+ runtime.client &&
210
+ isDeepStrictEqual(runtime.openCodeConfig, start.openCodeConfig)
211
+ ) {
212
+ return;
213
+ }
207
214
 
208
- if (start.tools && start.tools.length > 0) {
209
- runtime.toolNames = new Set(start.tools.map(tool => tool.name));
210
- runtime.relay = await startToolRelay({
211
- tools: start.tools,
212
- emit,
213
- requestToolResult: turn.requestToolResult,
215
+ closeRuntime();
216
+
217
+ try {
218
+ if (start.tools && start.tools.length > 0) {
219
+ runtime.toolNames = new Set(start.tools.map(tool => tool.name));
220
+ runtime.relay = await startToolRelay({
221
+ tools: start.tools,
222
+ emit,
223
+ requestToolResult: turn.requestToolResult,
224
+ });
225
+ }
226
+
227
+ const serverAuthHeaders = configureOpenCodeServerAuth({ env: procEnv });
228
+ const server = await createOpencodeServer({
229
+ hostname: '127.0.0.1',
230
+ port: 0,
231
+ timeout: 30_000,
232
+ config: buildOpenCodeConfig({
233
+ start,
234
+ relayPort: runtime.relay?.port,
235
+ }) as never,
236
+ });
237
+ runtime.server = server;
238
+ runtime.client = createOpencodeClient({
239
+ baseUrl: server.url,
240
+ directory: workdir,
241
+ headers: serverAuthHeaders,
214
242
  });
243
+ const mcpStatus = await runtime.client.mcp.status();
244
+ const mcpServers = asOpenCodeObject(mcpStatus.data) ?? {};
245
+ runtime.mcpToolPrefixes = new Set(
246
+ Object.entries(mcpServers)
247
+ .filter(
248
+ ([serverName, status]) =>
249
+ serverName !== 'harness-tools' &&
250
+ asOpenCodeObject(status)?.status === 'connected',
251
+ )
252
+ .map(([serverName]) => `${sanitizeMcpToolName(serverName)}_`),
253
+ );
254
+ runtime.openCodeConfig = structuredClone(start.openCodeConfig);
255
+ } catch (error) {
256
+ closeRuntime();
257
+ throw error;
215
258
  }
259
+ }
216
260
 
217
- const serverAuthHeaders = configureOpenCodeServerAuth({ env: procEnv });
218
- const server = await createOpencodeServer({
219
- hostname: '127.0.0.1',
220
- port: 0,
221
- timeout: 30_000,
222
- config: buildOpenCodeConfig({
223
- start,
224
- relayPort: runtime.relay?.port,
225
- }) as never,
226
- });
227
- runtime.server = server;
228
- runtime.client = createOpencodeClient({
229
- baseUrl: server.url,
230
- directory: workdir,
231
- headers: serverAuthHeaders,
232
- });
233
- const mcpStatus = await runtime.client.mcp.status();
234
- const mcpServers = asOpenCodeObject(mcpStatus.data) ?? {};
235
- runtime.mcpToolPrefixes = new Set(
236
- Object.entries(mcpServers)
237
- .filter(
238
- ([serverName, status]) =>
239
- serverName !== 'harness-tools' &&
240
- asOpenCodeObject(status)?.status === 'connected',
241
- )
242
- .map(([serverName]) => `${sanitizeMcpToolName(serverName)}_`),
243
- );
261
+ function closeRuntime(): void {
262
+ runtime.relay?.close();
263
+ runtime.server?.close();
264
+ runtime.server = undefined;
265
+ runtime.client = undefined;
266
+ runtime.relay = undefined;
267
+ runtime.openCodeConfig = undefined;
268
+ runtime.toolNames = new Set();
269
+ runtime.mcpToolPrefixes = new Set();
244
270
  }
245
271
 
246
272
  function buildOpenCodeConfig({
@@ -4,6 +4,8 @@ export type OpenCodeMessageInfo = {
4
4
  id?: unknown;
5
5
  sessionID?: unknown;
6
6
  role?: unknown;
7
+ summary?: unknown;
8
+ error?: unknown;
7
9
  type?: unknown;
8
10
  providerID?: unknown;
9
11
  modelID?: unknown;