@ai-sdk/harness-opencode 1.0.37 → 1.0.38
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/CHANGELOG.md +9 -0
- package/dist/bridge/index.mjs +141 -120
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/bridge/index.ts +5 -142
- package/src/bridge/opencode-events.ts +182 -0
package/dist/index.js
CHANGED
|
@@ -168,7 +168,7 @@ var inboundMessageSchema = z.discriminatedUnion("type", [
|
|
|
168
168
|
]);
|
|
169
169
|
|
|
170
170
|
// src/version.ts
|
|
171
|
-
var VERSION = true ? "1.0.
|
|
171
|
+
var VERSION = true ? "1.0.38" : "0.0.0-test";
|
|
172
172
|
|
|
173
173
|
// src/opencode-harness.ts
|
|
174
174
|
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.
|
|
3
|
+
"version": "1.0.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ws": "8.21.0",
|
|
30
|
-
"@ai-sdk/harness": "1.0.
|
|
30
|
+
"@ai-sdk/harness": "1.0.37",
|
|
31
31
|
"@ai-sdk/provider-utils": "5.0.11"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
package/src/bridge/index.ts
CHANGED
|
@@ -14,10 +14,14 @@ import {
|
|
|
14
14
|
createOpencodeServer,
|
|
15
15
|
} from '@opencode-ai/sdk/v2';
|
|
16
16
|
import {
|
|
17
|
+
createTranslationState,
|
|
18
|
+
emitLegacyPartDelta,
|
|
19
|
+
emitLegacyTextPartUpdate,
|
|
17
20
|
emitMissingFinalDelta,
|
|
18
21
|
getOpenCodeEventSessionId,
|
|
19
22
|
isStepSettlementEvent,
|
|
20
23
|
type OpenCodeEvent,
|
|
24
|
+
type TranslationState,
|
|
21
25
|
unwrapOpenCodeEvent,
|
|
22
26
|
} from './opencode-events';
|
|
23
27
|
import {
|
|
@@ -798,40 +802,6 @@ async function consumeEvents({
|
|
|
798
802
|
}
|
|
799
803
|
}
|
|
800
804
|
|
|
801
|
-
type TranslationState = {
|
|
802
|
-
textDeltas: Map<string, string>;
|
|
803
|
-
reasoningDeltas: Map<string, string>;
|
|
804
|
-
toolInputs: Map<string, string>;
|
|
805
|
-
toolNames: Map<string, { rawToolName: string; toolName: string }>;
|
|
806
|
-
toolCallsEmitted: Set<string>;
|
|
807
|
-
toolResultsEmitted: Set<string>;
|
|
808
|
-
hostToolCallsAuthorized: Set<string>;
|
|
809
|
-
shellCommands: Map<string, string>;
|
|
810
|
-
messageRoles: Map<string, string>;
|
|
811
|
-
turnUsage: Record<string, unknown> | undefined;
|
|
812
|
-
legacyTextPartIds: Set<string>;
|
|
813
|
-
legacyReasoningPartIds: Set<string>;
|
|
814
|
-
legacyStepFinishPartIds: Set<string>;
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
function createTranslationState(): TranslationState {
|
|
818
|
-
return {
|
|
819
|
-
textDeltas: new Map(),
|
|
820
|
-
reasoningDeltas: new Map(),
|
|
821
|
-
toolInputs: new Map(),
|
|
822
|
-
toolNames: new Map(),
|
|
823
|
-
toolCallsEmitted: new Set(),
|
|
824
|
-
toolResultsEmitted: new Set(),
|
|
825
|
-
hostToolCallsAuthorized: new Set(),
|
|
826
|
-
shellCommands: new Map(),
|
|
827
|
-
messageRoles: new Map(),
|
|
828
|
-
turnUsage: undefined,
|
|
829
|
-
legacyTextPartIds: new Set(),
|
|
830
|
-
legacyReasoningPartIds: new Set(),
|
|
831
|
-
legacyStepFinishPartIds: new Set(),
|
|
832
|
-
};
|
|
833
|
-
}
|
|
834
|
-
|
|
835
805
|
async function translateAndEmit({
|
|
836
806
|
event,
|
|
837
807
|
state,
|
|
@@ -865,32 +835,7 @@ async function translateAndEmit({
|
|
|
865
835
|
}
|
|
866
836
|
|
|
867
837
|
if (type === 'message.part.delta') {
|
|
868
|
-
|
|
869
|
-
const delta = String(props.delta ?? '');
|
|
870
|
-
if (!delta) return;
|
|
871
|
-
const messageID = stringValue(props.messageID);
|
|
872
|
-
if (messageID && state.messageRoles.get(messageID) === 'user') return;
|
|
873
|
-
if (field === 'text') {
|
|
874
|
-
const id = legacyPartId({ value: props, fallback: 'legacy-text' });
|
|
875
|
-
startLegacyPart({ ids: state.legacyTextPartIds, id, emit, type: 'text' });
|
|
876
|
-
state.textDeltas.set(id, `${state.textDeltas.get(id) ?? ''}${delta}`);
|
|
877
|
-
emit({ type: 'text-delta', id, delta });
|
|
878
|
-
return;
|
|
879
|
-
}
|
|
880
|
-
if (field === 'reasoning') {
|
|
881
|
-
const id = legacyPartId({ value: props, fallback: 'legacy-reasoning' });
|
|
882
|
-
startLegacyPart({
|
|
883
|
-
ids: state.legacyReasoningPartIds,
|
|
884
|
-
id,
|
|
885
|
-
emit,
|
|
886
|
-
type: 'reasoning',
|
|
887
|
-
});
|
|
888
|
-
state.reasoningDeltas.set(
|
|
889
|
-
id,
|
|
890
|
-
`${state.reasoningDeltas.get(id) ?? ''}${delta}`,
|
|
891
|
-
);
|
|
892
|
-
emit({ type: 'reasoning-delta', id, delta });
|
|
893
|
-
}
|
|
838
|
+
emitLegacyPartDelta({ props, state, emit });
|
|
894
839
|
return;
|
|
895
840
|
}
|
|
896
841
|
|
|
@@ -1142,88 +1087,6 @@ async function translateAndEmit({
|
|
|
1142
1087
|
}
|
|
1143
1088
|
}
|
|
1144
1089
|
|
|
1145
|
-
function legacyPartId({
|
|
1146
|
-
value,
|
|
1147
|
-
fallback,
|
|
1148
|
-
}: {
|
|
1149
|
-
value: Record<string, unknown>;
|
|
1150
|
-
fallback: string;
|
|
1151
|
-
}): string {
|
|
1152
|
-
return stringValue(value.partID) ?? stringValue(value.id) ?? fallback;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
function startLegacyPart({
|
|
1156
|
-
ids,
|
|
1157
|
-
id,
|
|
1158
|
-
emit,
|
|
1159
|
-
type,
|
|
1160
|
-
}: {
|
|
1161
|
-
ids: Set<string>;
|
|
1162
|
-
id: string;
|
|
1163
|
-
emit: Emit;
|
|
1164
|
-
type: 'text' | 'reasoning';
|
|
1165
|
-
}): void {
|
|
1166
|
-
if (ids.has(id)) return;
|
|
1167
|
-
ids.add(id);
|
|
1168
|
-
emit({ type: `${type}-start`, id });
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
function emitLegacyTextPartUpdate({
|
|
1172
|
-
part,
|
|
1173
|
-
state,
|
|
1174
|
-
emit,
|
|
1175
|
-
}: {
|
|
1176
|
-
part: unknown;
|
|
1177
|
-
state: TranslationState;
|
|
1178
|
-
emit: Emit;
|
|
1179
|
-
}): boolean {
|
|
1180
|
-
if (!isRecord(part)) return false;
|
|
1181
|
-
if (part.type !== 'text' && part.type !== 'reasoning') return false;
|
|
1182
|
-
const id = stringValue(part.id);
|
|
1183
|
-
if (!id) return true;
|
|
1184
|
-
|
|
1185
|
-
const messageID = stringValue(part.messageID);
|
|
1186
|
-
if (messageID && state.messageRoles.get(messageID) === 'user') return true;
|
|
1187
|
-
|
|
1188
|
-
const isReasoning = part.type === 'reasoning';
|
|
1189
|
-
const ids = isReasoning
|
|
1190
|
-
? state.legacyReasoningPartIds
|
|
1191
|
-
: state.legacyTextPartIds;
|
|
1192
|
-
const deltaMap = isReasoning ? state.reasoningDeltas : state.textDeltas;
|
|
1193
|
-
const deltaType = isReasoning ? 'reasoning-delta' : 'text-delta';
|
|
1194
|
-
const text = typeof part.text === 'string' ? part.text : undefined;
|
|
1195
|
-
|
|
1196
|
-
startLegacyPart({
|
|
1197
|
-
ids,
|
|
1198
|
-
id,
|
|
1199
|
-
emit,
|
|
1200
|
-
type: isReasoning ? 'reasoning' : 'text',
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
if (text !== undefined) {
|
|
1204
|
-
emitMissingFinalDelta({
|
|
1205
|
-
id,
|
|
1206
|
-
fullText: text,
|
|
1207
|
-
emittedText: deltaMap.get(id) ?? '',
|
|
1208
|
-
emit,
|
|
1209
|
-
type: deltaType,
|
|
1210
|
-
});
|
|
1211
|
-
deltaMap.set(id, text);
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
if (legacyPartEnded(part)) {
|
|
1215
|
-
ids.delete(id);
|
|
1216
|
-
deltaMap.delete(id);
|
|
1217
|
-
emit({ type: isReasoning ? 'reasoning-end' : 'text-end', id });
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
return true;
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
function legacyPartEnded(part: Record<string, unknown>): boolean {
|
|
1224
|
-
return isRecord(part.time) && part.time.end != null;
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
1090
|
function closeLegacyOpenParts({
|
|
1228
1091
|
state,
|
|
1229
1092
|
emit,
|
|
@@ -4,6 +4,42 @@ export type OpenCodeEvent = {
|
|
|
4
4
|
properties?: Record<string, any>;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
type Emit = (msg: Record<string, unknown>) => void;
|
|
8
|
+
|
|
9
|
+
export type TranslationState = {
|
|
10
|
+
textDeltas: Map<string, string>;
|
|
11
|
+
reasoningDeltas: Map<string, string>;
|
|
12
|
+
toolInputs: Map<string, string>;
|
|
13
|
+
toolNames: Map<string, { rawToolName: string; toolName: string }>;
|
|
14
|
+
toolCallsEmitted: Set<string>;
|
|
15
|
+
toolResultsEmitted: Set<string>;
|
|
16
|
+
hostToolCallsAuthorized: Set<string>;
|
|
17
|
+
shellCommands: Map<string, string>;
|
|
18
|
+
messageRoles: Map<string, string>;
|
|
19
|
+
turnUsage: Record<string, unknown> | undefined;
|
|
20
|
+
legacyTextPartIds: Set<string>;
|
|
21
|
+
legacyReasoningPartIds: Set<string>;
|
|
22
|
+
legacyStepFinishPartIds: Set<string>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function createTranslationState(): TranslationState {
|
|
26
|
+
return {
|
|
27
|
+
textDeltas: new Map(),
|
|
28
|
+
reasoningDeltas: new Map(),
|
|
29
|
+
toolInputs: new Map(),
|
|
30
|
+
toolNames: new Map(),
|
|
31
|
+
toolCallsEmitted: new Set(),
|
|
32
|
+
toolResultsEmitted: new Set(),
|
|
33
|
+
hostToolCallsAuthorized: new Set(),
|
|
34
|
+
shellCommands: new Map(),
|
|
35
|
+
messageRoles: new Map(),
|
|
36
|
+
turnUsage: undefined,
|
|
37
|
+
legacyTextPartIds: new Set(),
|
|
38
|
+
legacyReasoningPartIds: new Set(),
|
|
39
|
+
legacyStepFinishPartIds: new Set(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
7
43
|
export function unwrapOpenCodeEvent(
|
|
8
44
|
rawEvent: unknown,
|
|
9
45
|
): OpenCodeEvent | undefined {
|
|
@@ -77,6 +113,114 @@ export function emitMissingFinalDelta({
|
|
|
77
113
|
emit({ type, id, delta: fullText.slice(emittedText.length) });
|
|
78
114
|
}
|
|
79
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Translates an OpenCode `message.part.delta` event (a streaming text or
|
|
118
|
+
* reasoning delta) into legacy stream parts.
|
|
119
|
+
*/
|
|
120
|
+
export function emitLegacyPartDelta({
|
|
121
|
+
props,
|
|
122
|
+
state,
|
|
123
|
+
emit,
|
|
124
|
+
}: {
|
|
125
|
+
props: Record<string, unknown>;
|
|
126
|
+
state: TranslationState;
|
|
127
|
+
emit: Emit;
|
|
128
|
+
}): void {
|
|
129
|
+
const field = String(props.field ?? '');
|
|
130
|
+
const delta = String(props.delta ?? '');
|
|
131
|
+
if (!delta) return;
|
|
132
|
+
const messageID = stringValue(props.messageID);
|
|
133
|
+
if (messageID && state.messageRoles.get(messageID) === 'user') return;
|
|
134
|
+
if (field === 'text') {
|
|
135
|
+
const id = legacyPartId({ value: props, fallback: 'legacy-text' });
|
|
136
|
+
// OpenCode publishes reasoning deltas with field:"text". If this id was
|
|
137
|
+
// already announced as a reasoning part, route it to reasoning so it is not
|
|
138
|
+
// duplicated as a text part.
|
|
139
|
+
if (state.legacyReasoningPartIds.has(id)) {
|
|
140
|
+
state.reasoningDeltas.set(
|
|
141
|
+
id,
|
|
142
|
+
`${state.reasoningDeltas.get(id) ?? ''}${delta}`,
|
|
143
|
+
);
|
|
144
|
+
emit({ type: 'reasoning-delta', id, delta });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
startLegacyPart({ ids: state.legacyTextPartIds, id, emit, type: 'text' });
|
|
148
|
+
state.textDeltas.set(id, `${state.textDeltas.get(id) ?? ''}${delta}`);
|
|
149
|
+
emit({ type: 'text-delta', id, delta });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (field === 'reasoning') {
|
|
153
|
+
const id = legacyPartId({ value: props, fallback: 'legacy-reasoning' });
|
|
154
|
+
startLegacyPart({
|
|
155
|
+
ids: state.legacyReasoningPartIds,
|
|
156
|
+
id,
|
|
157
|
+
emit,
|
|
158
|
+
type: 'reasoning',
|
|
159
|
+
});
|
|
160
|
+
state.reasoningDeltas.set(
|
|
161
|
+
id,
|
|
162
|
+
`${state.reasoningDeltas.get(id) ?? ''}${delta}`,
|
|
163
|
+
);
|
|
164
|
+
emit({ type: 'reasoning-delta', id, delta });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Translates an OpenCode `message.part.updated` event for a text or reasoning
|
|
170
|
+
* part. Returns `true` when it handled the part.
|
|
171
|
+
*/
|
|
172
|
+
export function emitLegacyTextPartUpdate({
|
|
173
|
+
part,
|
|
174
|
+
state,
|
|
175
|
+
emit,
|
|
176
|
+
}: {
|
|
177
|
+
part: unknown;
|
|
178
|
+
state: TranslationState;
|
|
179
|
+
emit: Emit;
|
|
180
|
+
}): boolean {
|
|
181
|
+
if (!isRecord(part)) return false;
|
|
182
|
+
if (part.type !== 'text' && part.type !== 'reasoning') return false;
|
|
183
|
+
const id = stringValue(part.id);
|
|
184
|
+
if (!id) return true;
|
|
185
|
+
|
|
186
|
+
const messageID = stringValue(part.messageID);
|
|
187
|
+
if (messageID && state.messageRoles.get(messageID) === 'user') return true;
|
|
188
|
+
|
|
189
|
+
const isReasoning = part.type === 'reasoning';
|
|
190
|
+
const ids = isReasoning
|
|
191
|
+
? state.legacyReasoningPartIds
|
|
192
|
+
: state.legacyTextPartIds;
|
|
193
|
+
const deltaMap = isReasoning ? state.reasoningDeltas : state.textDeltas;
|
|
194
|
+
const deltaType = isReasoning ? 'reasoning-delta' : 'text-delta';
|
|
195
|
+
const text = typeof part.text === 'string' ? part.text : undefined;
|
|
196
|
+
|
|
197
|
+
startLegacyPart({
|
|
198
|
+
ids,
|
|
199
|
+
id,
|
|
200
|
+
emit,
|
|
201
|
+
type: isReasoning ? 'reasoning' : 'text',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (text !== undefined) {
|
|
205
|
+
emitMissingFinalDelta({
|
|
206
|
+
id,
|
|
207
|
+
fullText: text,
|
|
208
|
+
emittedText: deltaMap.get(id) ?? '',
|
|
209
|
+
emit,
|
|
210
|
+
type: deltaType,
|
|
211
|
+
});
|
|
212
|
+
deltaMap.set(id, text);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (legacyPartEnded(part)) {
|
|
216
|
+
ids.delete(id);
|
|
217
|
+
deltaMap.delete(id);
|
|
218
|
+
emit({ type: isReasoning ? 'reasoning-end' : 'text-end', id });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
80
224
|
function stripSyncVersion(type: string): string {
|
|
81
225
|
return type.replace(/\.\d+$/, '');
|
|
82
226
|
}
|
|
@@ -86,3 +230,41 @@ function asRecord(value: unknown): Record<string, any> | undefined {
|
|
|
86
230
|
return undefined;
|
|
87
231
|
return value as Record<string, any>;
|
|
88
232
|
}
|
|
233
|
+
|
|
234
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
235
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function stringValue(value: unknown): string | undefined {
|
|
239
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function legacyPartId({
|
|
243
|
+
value,
|
|
244
|
+
fallback,
|
|
245
|
+
}: {
|
|
246
|
+
value: Record<string, unknown>;
|
|
247
|
+
fallback: string;
|
|
248
|
+
}): string {
|
|
249
|
+
return stringValue(value.partID) ?? stringValue(value.id) ?? fallback;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function startLegacyPart({
|
|
253
|
+
ids,
|
|
254
|
+
id,
|
|
255
|
+
emit,
|
|
256
|
+
type,
|
|
257
|
+
}: {
|
|
258
|
+
ids: Set<string>;
|
|
259
|
+
id: string;
|
|
260
|
+
emit: Emit;
|
|
261
|
+
type: 'text' | 'reasoning';
|
|
262
|
+
}): void {
|
|
263
|
+
if (ids.has(id)) return;
|
|
264
|
+
ids.add(id);
|
|
265
|
+
emit({ type: `${type}-start`, id });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function legacyPartEnded(part: Record<string, unknown>): boolean {
|
|
269
|
+
return isRecord(part.time) && part.time.end != null;
|
|
270
|
+
}
|