@ai-sdk/harness-opencode 1.0.95 → 1.0.97
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 +21 -0
- package/dist/bridge/index.mjs +64 -1
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +30 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/bridge/index.ts +71 -1
- package/src/opencode-bootstrap.ts +19 -15
- package/src/opencode-harness.ts +14 -21
package/src/bridge/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
createTranslationState,
|
|
17
17
|
emitOpenCodeStreamStart,
|
|
18
18
|
getOpenCodeEventSessionId,
|
|
19
|
+
openCodeMessageInfoFromValue,
|
|
19
20
|
type TranslationState,
|
|
20
21
|
unwrapOpenCodeEvent,
|
|
21
22
|
} from './opencode-events';
|
|
@@ -144,6 +145,7 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
144
145
|
await client.instance.dispose({ directory: workdir });
|
|
145
146
|
}
|
|
146
147
|
const sessionId = await ensureSession({ client, start, emit });
|
|
148
|
+
await switchSessionModel({ client, sessionId, start });
|
|
147
149
|
|
|
148
150
|
if (start.operation === 'compact') {
|
|
149
151
|
await runCompaction({ client, sessionId, start, turn, emit });
|
|
@@ -162,6 +164,27 @@ async function runTurn(start: StartMessage, turn: BridgeTurn): Promise<void> {
|
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
|
|
167
|
+
async function switchSessionModel({
|
|
168
|
+
client,
|
|
169
|
+
sessionId,
|
|
170
|
+
start,
|
|
171
|
+
}: {
|
|
172
|
+
client: OpenCodeClient;
|
|
173
|
+
sessionId: string;
|
|
174
|
+
start: StartMessage;
|
|
175
|
+
}): Promise<void> {
|
|
176
|
+
const model = modelRefFromStart(start);
|
|
177
|
+
if (model == null) return;
|
|
178
|
+
const response = await client.v2.session.switchModel({
|
|
179
|
+
sessionID: sessionId,
|
|
180
|
+
model: {
|
|
181
|
+
id: model.modelID,
|
|
182
|
+
providerID: model.providerID,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (response.error != null) throw response.error;
|
|
186
|
+
}
|
|
187
|
+
|
|
165
188
|
async function ensureRuntime({
|
|
166
189
|
start,
|
|
167
190
|
turn,
|
|
@@ -943,9 +966,33 @@ async function consumeEvents({
|
|
|
943
966
|
let processEvent = descendantEventProcessors.get(descendantSessionId);
|
|
944
967
|
if (!processEvent) {
|
|
945
968
|
const descendantState = createTranslationState();
|
|
969
|
+
let currentEvent: OpenCodeEvent | undefined;
|
|
970
|
+
let modelId: string | undefined;
|
|
971
|
+
const emittedUsageStepIds = new Set<string>();
|
|
946
972
|
processEvent = createEmitStreamEvent({
|
|
947
973
|
state: descendantState,
|
|
948
|
-
emit:
|
|
974
|
+
emit: message => {
|
|
975
|
+
if (message.type !== 'finish-step') return;
|
|
976
|
+
const stepId = getSubagentStepId(currentEvent);
|
|
977
|
+
if (!stepId || emittedUsageStepIds.has(stepId)) return;
|
|
978
|
+
emittedUsageStepIds.add(stepId);
|
|
979
|
+
const opencodeMetadata = asOpenCodeObject(
|
|
980
|
+
message.harnessMetadata,
|
|
981
|
+
)?.opencode;
|
|
982
|
+
const cost = asOpenCodeObject(opencodeMetadata)?.cost;
|
|
983
|
+
emit({
|
|
984
|
+
type: 'raw',
|
|
985
|
+
rawValue: {
|
|
986
|
+
type: 'opencode.subagent-usage',
|
|
987
|
+
version: 1,
|
|
988
|
+
sessionId: descendantSessionId,
|
|
989
|
+
stepId,
|
|
990
|
+
...(modelId ? { modelId } : {}),
|
|
991
|
+
usage: message.usage,
|
|
992
|
+
...(typeof cost === 'number' ? { cost } : {}),
|
|
993
|
+
},
|
|
994
|
+
});
|
|
995
|
+
},
|
|
949
996
|
emitWarning: () => undefined,
|
|
950
997
|
emitError: () => undefined,
|
|
951
998
|
toWireToolName,
|
|
@@ -958,6 +1005,19 @@ async function consumeEvents({
|
|
|
958
1005
|
stripWorkDir,
|
|
959
1006
|
formatError,
|
|
960
1007
|
});
|
|
1008
|
+
const emitDescendantEvent = processEvent;
|
|
1009
|
+
processEvent = descendantEvent => {
|
|
1010
|
+
currentEvent = descendantEvent;
|
|
1011
|
+
if (descendantEvent.type === 'message.updated') {
|
|
1012
|
+
const info = openCodeMessageInfoFromValue(
|
|
1013
|
+
descendantEvent.properties?.info,
|
|
1014
|
+
);
|
|
1015
|
+
const providerID = stringValue(info?.providerID);
|
|
1016
|
+
const modelID = stringValue(info?.modelID);
|
|
1017
|
+
if (providerID && modelID) modelId = `${providerID}/${modelID}`;
|
|
1018
|
+
}
|
|
1019
|
+
emitDescendantEvent(descendantEvent);
|
|
1020
|
+
};
|
|
961
1021
|
descendantEventProcessors.set(descendantSessionId, processEvent);
|
|
962
1022
|
}
|
|
963
1023
|
processEvent(event);
|
|
@@ -1005,6 +1065,16 @@ async function consumeEvents({
|
|
|
1005
1065
|
}
|
|
1006
1066
|
}
|
|
1007
1067
|
|
|
1068
|
+
function getSubagentStepId(event: OpenCodeEvent | undefined) {
|
|
1069
|
+
if (event?.type === 'message.part.updated') {
|
|
1070
|
+
const part = asOpenCodeObject(event.properties?.part);
|
|
1071
|
+
if (part?.type !== 'step-finish') return undefined;
|
|
1072
|
+
return stringValue(part.id) ?? stringValue(part.messageID) ?? event.id;
|
|
1073
|
+
}
|
|
1074
|
+
if (event?.type !== 'session.next.step.ended') return undefined;
|
|
1075
|
+
return stringValue(event.properties?.stepID) ?? event.id;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1008
1078
|
function sanitizeMcpToolName(value: string): string {
|
|
1009
1079
|
return value.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
1010
1080
|
}
|
|
@@ -44,19 +44,23 @@ export async function getOpenCodeBootstrap(): Promise<HarnessV1Bootstrap> {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async function readBridgeAsset(name: string): Promise<string> {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
return readFile(
|
|
48
|
+
fileURLToPath(
|
|
49
|
+
resolveBridgeAssetUrl({
|
|
50
|
+
name,
|
|
51
|
+
moduleUrl: import.meta.url,
|
|
52
|
+
}),
|
|
53
|
+
),
|
|
54
|
+
'utf8',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveBridgeAssetUrl({
|
|
59
|
+
name,
|
|
60
|
+
moduleUrl,
|
|
61
|
+
}: {
|
|
62
|
+
name: string;
|
|
63
|
+
moduleUrl: string | URL;
|
|
64
|
+
}): URL {
|
|
65
|
+
return new URL(`./bridge/${name}`, moduleUrl);
|
|
62
66
|
}
|
package/src/opencode-harness.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import {
|
|
4
3
|
commonTool,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
applyCredentialForwarding,
|
|
25
24
|
classifyDiskLog,
|
|
26
25
|
createSandboxCredentialEnvironment,
|
|
26
|
+
createBridgeToken,
|
|
27
27
|
experimental_createBridgeUserMessageSubmitter,
|
|
28
28
|
createBridgeErrorHandler,
|
|
29
29
|
createBridgeStartupError,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
shellQuote,
|
|
38
38
|
warnCredentialBrokeringUnavailable,
|
|
39
39
|
waitForBridgeReady,
|
|
40
|
+
withBridgeToken,
|
|
40
41
|
writeSkills as writeHarnessSkills,
|
|
41
42
|
type WriteSkillsResult,
|
|
42
43
|
} from '@ai-sdk/harness/utils';
|
|
@@ -256,7 +257,7 @@ export function createOpenCode(
|
|
|
256
257
|
lifecycleStateSchema: openCodeResumeStateSchema,
|
|
257
258
|
getBootstrap: getOpenCodeBootstrap,
|
|
258
259
|
doStart: async startOpts => {
|
|
259
|
-
const configuredModel =
|
|
260
|
+
const configuredModel = settings.model;
|
|
260
261
|
const sandboxSession = startOpts.sandboxSession;
|
|
261
262
|
const toolSafeSandboxSession =
|
|
262
263
|
getRestrictedSandboxSession(sandboxSession);
|
|
@@ -446,7 +447,7 @@ export function createOpenCode(
|
|
|
446
447
|
});
|
|
447
448
|
const token =
|
|
448
449
|
settings.mintBridgeToken == null
|
|
449
|
-
?
|
|
450
|
+
? createBridgeToken()
|
|
450
451
|
: settings.mintBridgeToken(sandboxId!);
|
|
451
452
|
const forwardedAuthEnvironment = credentialsBrokered
|
|
452
453
|
? sandboxAuthEnvironment
|
|
@@ -764,18 +765,6 @@ function webSocketMessageToString(raw: unknown): string {
|
|
|
764
765
|
return String(raw);
|
|
765
766
|
}
|
|
766
767
|
|
|
767
|
-
function withBridgeToken({
|
|
768
|
-
endpoint,
|
|
769
|
-
token,
|
|
770
|
-
}: {
|
|
771
|
-
endpoint: HarnessV1PortEndpoint;
|
|
772
|
-
token: string;
|
|
773
|
-
}): HarnessV1PortEndpoint {
|
|
774
|
-
const bridgeUrl = new URL(endpoint.url);
|
|
775
|
-
bridgeUrl.searchParams.set('agent_bridge_token', token);
|
|
776
|
-
return { ...endpoint, url: bridgeUrl.toString() };
|
|
777
|
-
}
|
|
778
|
-
|
|
779
768
|
function createSession({
|
|
780
769
|
sessionId,
|
|
781
770
|
channel,
|
|
@@ -829,6 +818,7 @@ function createSession({
|
|
|
829
818
|
let pendingResumeSessionId = seedResumeSessionOnFirstPrompt
|
|
830
819
|
? openCodeSessionId
|
|
831
820
|
: undefined;
|
|
821
|
+
let selectedModel = model;
|
|
832
822
|
let activeTurn = false;
|
|
833
823
|
const pendingCompactionParts: HarnessV1StreamPart[] = [];
|
|
834
824
|
|
|
@@ -979,8 +969,8 @@ function createSession({
|
|
|
979
969
|
};
|
|
980
970
|
};
|
|
981
971
|
|
|
982
|
-
const startBase = () => ({
|
|
983
|
-
model,
|
|
972
|
+
const startBase = (turnModel: string | undefined) => ({
|
|
973
|
+
model: turnModel,
|
|
984
974
|
provider,
|
|
985
975
|
...(reasoningVariant ? { variant: reasoningVariant } : {}),
|
|
986
976
|
...(openCodeConfig == null ? {} : { openCodeConfig }),
|
|
@@ -998,7 +988,6 @@ function createSession({
|
|
|
998
988
|
return {
|
|
999
989
|
sessionId,
|
|
1000
990
|
isResume,
|
|
1001
|
-
modelId: model,
|
|
1002
991
|
doPromptTurn: async promptOpts => {
|
|
1003
992
|
if (
|
|
1004
993
|
promptOpts.responseFormat?.type === 'json' &&
|
|
@@ -1020,6 +1009,8 @@ function createSession({
|
|
|
1020
1009
|
emit: promptOpts.emit,
|
|
1021
1010
|
abortSignal: promptOpts.abortSignal,
|
|
1022
1011
|
});
|
|
1012
|
+
const turnModel = promptOpts.model ?? selectedModel;
|
|
1013
|
+
if (turnModel) selectedModel = turnModel;
|
|
1023
1014
|
channel.send({
|
|
1024
1015
|
type: 'start',
|
|
1025
1016
|
operation: 'prompt',
|
|
@@ -1036,7 +1027,7 @@ function createSession({
|
|
|
1036
1027
|
? { instructions: promptOpts.instructions }
|
|
1037
1028
|
: {}),
|
|
1038
1029
|
skillsChanged: skillWriteResult.changed,
|
|
1039
|
-
...startBase(),
|
|
1030
|
+
...startBase(turnModel),
|
|
1040
1031
|
});
|
|
1041
1032
|
pendingResumeSessionId = undefined;
|
|
1042
1033
|
return control;
|
|
@@ -1063,6 +1054,8 @@ function createSession({
|
|
|
1063
1054
|
abortSignal: continueOpts.abortSignal,
|
|
1064
1055
|
});
|
|
1065
1056
|
if (rerunContinue) {
|
|
1057
|
+
const turnModel = continueOpts.model ?? selectedModel;
|
|
1058
|
+
if (turnModel) selectedModel = turnModel;
|
|
1066
1059
|
channel.send({
|
|
1067
1060
|
type: 'start',
|
|
1068
1061
|
operation: 'prompt',
|
|
@@ -1079,7 +1072,7 @@ function createSession({
|
|
|
1079
1072
|
? { instructions: continueOpts.instructions }
|
|
1080
1073
|
: {}),
|
|
1081
1074
|
skillsChanged: skillWriteResult.changed,
|
|
1082
|
-
...startBase(),
|
|
1075
|
+
...startBase(turnModel),
|
|
1083
1076
|
});
|
|
1084
1077
|
pendingResumeSessionId = undefined;
|
|
1085
1078
|
}
|
|
@@ -1102,7 +1095,7 @@ function createSession({
|
|
|
1102
1095
|
}
|
|
1103
1096
|
await runCompactOperation({
|
|
1104
1097
|
channel,
|
|
1105
|
-
model,
|
|
1098
|
+
model: selectedModel,
|
|
1106
1099
|
provider,
|
|
1107
1100
|
permissionMode,
|
|
1108
1101
|
debug,
|