@ai-sdk/harness-opencode 0.0.0-cca10482-20260708215408
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 +182 -0
- package/LICENSE +13 -0
- package/README.md +6 -0
- package/dist/bridge/host-tool-mcp.mjs +101 -0
- package/dist/bridge/host-tool-mcp.mjs.map +1 -0
- package/dist/bridge/index.mjs +2194 -0
- package/dist/bridge/index.mjs.map +1 -0
- package/dist/bridge/package.json +13 -0
- package/dist/bridge/pnpm-lock.yaml +933 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +999 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
- package/src/bridge/host-tool-mcp.ts +134 -0
- package/src/bridge/index.ts +1993 -0
- package/src/bridge/opencode-events.ts +88 -0
- package/src/bridge/opencode-path.ts +17 -0
- package/src/bridge/opencode-usage.ts +156 -0
- package/src/bridge/package.json +13 -0
- package/src/bridge/pnpm-lock.yaml +933 -0
- package/src/bridge/tool-relay-auth.ts +151 -0
- package/src/index.ts +12 -0
- package/src/opencode-auth.ts +175 -0
- package/src/opencode-bridge-protocol.ts +29 -0
- package/src/opencode-harness.ts +1020 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type OpenCodeEvent = {
|
|
2
|
+
id?: string;
|
|
3
|
+
type?: string;
|
|
4
|
+
properties?: Record<string, any>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function unwrapOpenCodeEvent(
|
|
8
|
+
rawEvent: unknown,
|
|
9
|
+
): OpenCodeEvent | undefined {
|
|
10
|
+
if (!rawEvent || typeof rawEvent !== 'object') return undefined;
|
|
11
|
+
const raw = rawEvent as Record<string, any>;
|
|
12
|
+
if (raw.type === 'sync' && raw.syncEvent) {
|
|
13
|
+
const sync = raw.syncEvent as Record<string, any>;
|
|
14
|
+
return {
|
|
15
|
+
id: String(sync.id ?? raw.id ?? ''),
|
|
16
|
+
type: stripSyncVersion(String(sync.type ?? '')),
|
|
17
|
+
properties: asRecord(sync.data) ?? {},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
id: typeof raw.id === 'string' ? raw.id : undefined,
|
|
22
|
+
type: typeof raw.type === 'string' ? stripSyncVersion(raw.type) : undefined,
|
|
23
|
+
properties: asRecord(raw.properties) ?? asRecord(raw.data) ?? {},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getOpenCodeEventSessionId(
|
|
28
|
+
event: OpenCodeEvent,
|
|
29
|
+
): string | undefined {
|
|
30
|
+
const props = event.properties;
|
|
31
|
+
if (!props) return undefined;
|
|
32
|
+
if (typeof props.sessionID === 'string') return props.sessionID;
|
|
33
|
+
if (typeof props.sessionId === 'string') return props.sessionId;
|
|
34
|
+
if (event.type?.startsWith('session.') && typeof props.id === 'string') {
|
|
35
|
+
return props.id;
|
|
36
|
+
}
|
|
37
|
+
const part = props.part;
|
|
38
|
+
if (
|
|
39
|
+
part &&
|
|
40
|
+
typeof part === 'object' &&
|
|
41
|
+
!Array.isArray(part) &&
|
|
42
|
+
typeof (part as { sessionID?: unknown }).sessionID === 'string'
|
|
43
|
+
) {
|
|
44
|
+
return (part as { sessionID: string }).sessionID;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isStepSettlementEvent(event: OpenCodeEvent): boolean {
|
|
50
|
+
return (
|
|
51
|
+
event.type === 'session.next.step.ended' ||
|
|
52
|
+
event.type === 'session.next.step.failed' ||
|
|
53
|
+
event.type === 'session.error'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function emitMissingFinalDelta({
|
|
58
|
+
id,
|
|
59
|
+
fullText,
|
|
60
|
+
emittedText,
|
|
61
|
+
emit,
|
|
62
|
+
type,
|
|
63
|
+
}: {
|
|
64
|
+
id: string;
|
|
65
|
+
fullText: string | undefined;
|
|
66
|
+
emittedText: string;
|
|
67
|
+
emit: (msg: Record<string, unknown>) => void;
|
|
68
|
+
type: 'text-delta' | 'reasoning-delta';
|
|
69
|
+
}): void {
|
|
70
|
+
if (
|
|
71
|
+
!fullText ||
|
|
72
|
+
fullText === emittedText ||
|
|
73
|
+
!fullText.startsWith(emittedText)
|
|
74
|
+
) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
emit({ type, id, delta: fullText.slice(emittedText.length) });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function stripSyncVersion(type: string): string {
|
|
81
|
+
return type.replace(/\.\d+$/, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function asRecord(value: unknown): Record<string, any> | undefined {
|
|
85
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
86
|
+
return undefined;
|
|
87
|
+
return value as Record<string, any>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
const fallbackPath =
|
|
4
|
+
'/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
|
5
|
+
|
|
6
|
+
export function prependOpenCodeBinToPath({
|
|
7
|
+
bootstrapDir,
|
|
8
|
+
env,
|
|
9
|
+
}: {
|
|
10
|
+
bootstrapDir: string;
|
|
11
|
+
env: NodeJS.ProcessEnv;
|
|
12
|
+
}): void {
|
|
13
|
+
env.PATH = [
|
|
14
|
+
path.join(bootstrapDir, 'node_modules', '.bin'),
|
|
15
|
+
env.PATH || fallbackPath,
|
|
16
|
+
].join(path.delimiter);
|
|
17
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export type OpenCodeTokenUsage = {
|
|
2
|
+
readonly input: number;
|
|
3
|
+
readonly output: number;
|
|
4
|
+
readonly reasoning: number;
|
|
5
|
+
readonly cache: {
|
|
6
|
+
readonly read: number;
|
|
7
|
+
readonly write: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type HarnessUsage = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
export function mapUsage(tokens: unknown): HarnessUsage {
|
|
14
|
+
const value = extractOpenCodeTokens(tokens) ?? zeroOpenCodeTokens();
|
|
15
|
+
const cacheRead = value.cache.read;
|
|
16
|
+
return {
|
|
17
|
+
inputTokens: {
|
|
18
|
+
total: value.input,
|
|
19
|
+
noCache: Math.max(0, value.input - cacheRead),
|
|
20
|
+
cacheRead,
|
|
21
|
+
cacheWrite: value.cache.write,
|
|
22
|
+
},
|
|
23
|
+
outputTokens: {
|
|
24
|
+
total: value.output + value.reasoning,
|
|
25
|
+
text: value.output,
|
|
26
|
+
reasoning: value.reasoning,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function defaultUsage(): HarnessUsage {
|
|
32
|
+
return mapUsage(zeroOpenCodeTokens());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function extractSessionTokens(
|
|
36
|
+
value: unknown,
|
|
37
|
+
): OpenCodeTokenUsage | undefined {
|
|
38
|
+
const record = asRecord(value);
|
|
39
|
+
if (!record) return undefined;
|
|
40
|
+
const tokens =
|
|
41
|
+
extractOpenCodeTokens(record.tokens) ??
|
|
42
|
+
extractOpenCodeTokens(asRecord(record.info)?.tokens) ??
|
|
43
|
+
extractOpenCodeTokens(asRecord(record.data)?.tokens) ??
|
|
44
|
+
extractOpenCodeTokens(asRecord(asRecord(record.data)?.data)?.tokens);
|
|
45
|
+
return tokens;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function subtractSessionTokens({
|
|
49
|
+
before,
|
|
50
|
+
after,
|
|
51
|
+
}: {
|
|
52
|
+
before: OpenCodeTokenUsage;
|
|
53
|
+
after: OpenCodeTokenUsage;
|
|
54
|
+
}): OpenCodeTokenUsage {
|
|
55
|
+
return {
|
|
56
|
+
input: diff({ before: before.input, after: after.input }),
|
|
57
|
+
output: diff({ before: before.output, after: after.output }),
|
|
58
|
+
reasoning: diff({ before: before.reasoning, after: after.reasoning }),
|
|
59
|
+
cache: {
|
|
60
|
+
read: diff({ before: before.cache.read, after: after.cache.read }),
|
|
61
|
+
write: diff({ before: before.cache.write, after: after.cache.write }),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function addUsage({
|
|
67
|
+
left,
|
|
68
|
+
right,
|
|
69
|
+
}: {
|
|
70
|
+
left: HarnessUsage | undefined;
|
|
71
|
+
right: HarnessUsage;
|
|
72
|
+
}): HarnessUsage {
|
|
73
|
+
if (left == null) return right;
|
|
74
|
+
const leftInput = asTokenGroup(left.inputTokens);
|
|
75
|
+
const rightInput = asTokenGroup(right.inputTokens);
|
|
76
|
+
const leftOutput = asTokenGroup(left.outputTokens);
|
|
77
|
+
const rightOutput = asTokenGroup(right.outputTokens);
|
|
78
|
+
return {
|
|
79
|
+
inputTokens: {
|
|
80
|
+
total: add({ left: leftInput.total, right: rightInput.total }),
|
|
81
|
+
noCache: add({ left: leftInput.noCache, right: rightInput.noCache }),
|
|
82
|
+
cacheRead: add({
|
|
83
|
+
left: leftInput.cacheRead,
|
|
84
|
+
right: rightInput.cacheRead,
|
|
85
|
+
}),
|
|
86
|
+
cacheWrite: add({
|
|
87
|
+
left: leftInput.cacheWrite,
|
|
88
|
+
right: rightInput.cacheWrite,
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
outputTokens: {
|
|
92
|
+
total: add({ left: leftOutput.total, right: rightOutput.total }),
|
|
93
|
+
text: add({ left: leftOutput.text, right: rightOutput.text }),
|
|
94
|
+
reasoning: add({
|
|
95
|
+
left: leftOutput.reasoning,
|
|
96
|
+
right: rightOutput.reasoning,
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function extractOpenCodeTokens(value: unknown): OpenCodeTokenUsage | undefined {
|
|
103
|
+
const record = asRecord(value);
|
|
104
|
+
const cache = asRecord(record?.cache);
|
|
105
|
+
if (!record || !cache) return undefined;
|
|
106
|
+
return {
|
|
107
|
+
input: numberValue(record.input),
|
|
108
|
+
output: numberValue(record.output),
|
|
109
|
+
reasoning: numberValue(record.reasoning),
|
|
110
|
+
cache: {
|
|
111
|
+
read: numberValue(cache.read),
|
|
112
|
+
write: numberValue(cache.write),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function zeroOpenCodeTokens(): OpenCodeTokenUsage {
|
|
118
|
+
return {
|
|
119
|
+
input: 0,
|
|
120
|
+
output: 0,
|
|
121
|
+
reasoning: 0,
|
|
122
|
+
cache: { read: 0, write: 0 },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function asTokenGroup(value: unknown): Record<string, number | undefined> {
|
|
127
|
+
return asRecord(value) ?? {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function asRecord(value: unknown): Record<string, any> | undefined {
|
|
131
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
132
|
+
return undefined;
|
|
133
|
+
return value as Record<string, any>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function numberValue(value: unknown): number {
|
|
137
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function diff({ before, after }: { before: number; after: number }): number {
|
|
141
|
+
return Math.max(0, after - before);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function add({
|
|
145
|
+
left,
|
|
146
|
+
right,
|
|
147
|
+
}: {
|
|
148
|
+
left: unknown;
|
|
149
|
+
right: unknown;
|
|
150
|
+
}): number | undefined {
|
|
151
|
+
const leftNumber = typeof left === 'number' ? left : undefined;
|
|
152
|
+
const rightNumber = typeof right === 'number' ? right : undefined;
|
|
153
|
+
return leftNumber == null && rightNumber == null
|
|
154
|
+
? undefined
|
|
155
|
+
: (leftNumber ?? 0) + (rightNumber ?? 0);
|
|
156
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "harness-opencode-bridge",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
8
|
+
"@opencode-ai/sdk": "1.17.6",
|
|
9
|
+
"opencode-ai": "1.17.6",
|
|
10
|
+
"ws": "8.21.0",
|
|
11
|
+
"zod": "3.25.76"
|
|
12
|
+
}
|
|
13
|
+
}
|