@ai-sdk/harness-opencode 1.0.51 → 1.0.53
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 +16 -0
- package/dist/bridge/index.mjs +125 -78
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/bridge/create-emit-stream-event.ts +114 -49
- package/src/bridge/index.ts +19 -21
- package/src/bridge/opencode-events.ts +62 -37
- package/src/bridge/opencode-finish-step.ts +14 -3
- package/src/bridge/opencode-types.ts +33 -0
- package/src/bridge/opencode-usage.ts +21 -12
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.53" : "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.53",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ws": "8.21.0",
|
|
30
|
-
"@ai-sdk/harness": "1.0.
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
30
|
+
"@ai-sdk/harness": "1.0.52",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.17"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"zod": "^3.25.76 || ^4.1.8"
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type { BridgeTurn } from '@ai-sdk/harness/bridge';
|
|
2
|
-
import { isRecord } from '@ai-sdk/provider-utils';
|
|
3
2
|
import {
|
|
4
3
|
emitLegacyPartDelta,
|
|
5
4
|
emitLegacyTextPartUpdate,
|
|
6
5
|
emitMissingFinalDelta,
|
|
7
|
-
|
|
6
|
+
openCodeMessageInfoFromValue,
|
|
8
7
|
type TranslationState,
|
|
9
8
|
} from './opencode-events';
|
|
10
9
|
import {
|
|
11
|
-
legacyStepFinishPartToFinishStep,
|
|
12
10
|
mapOpenCodeFinishReason,
|
|
11
|
+
translateLegacyStepFinishPart,
|
|
13
12
|
} from './opencode-finish-step';
|
|
14
13
|
import { mapUsage } from './opencode-usage';
|
|
14
|
+
import {
|
|
15
|
+
asOpenCodeObject,
|
|
16
|
+
type OpenCodeEvent,
|
|
17
|
+
type OpenCodeEventProperties,
|
|
18
|
+
} from './opencode-types';
|
|
15
19
|
|
|
16
20
|
type Emit = (message: Record<string, unknown>) => void;
|
|
17
21
|
|
|
@@ -52,8 +56,8 @@ export function createEmitStreamEvent({
|
|
|
52
56
|
const props = event.properties ?? {};
|
|
53
57
|
|
|
54
58
|
if (type === 'message.updated') {
|
|
55
|
-
const info = props.info;
|
|
56
|
-
if (
|
|
59
|
+
const info = openCodeMessageInfoFromValue(props.info);
|
|
60
|
+
if (info) {
|
|
57
61
|
const id = stringValue(info.id);
|
|
58
62
|
const role = stringValue(info.role);
|
|
59
63
|
if (id && role) state.messageRoles.set(id, role);
|
|
@@ -238,7 +242,7 @@ export function createEmitStreamEvent({
|
|
|
238
242
|
}
|
|
239
243
|
if (type === 'session.next.retried') {
|
|
240
244
|
const error = props.error ?? event;
|
|
241
|
-
if (
|
|
245
|
+
if (openCodeErrorFromValue(error)?.isRetryable === false) {
|
|
242
246
|
emitError({
|
|
243
247
|
error,
|
|
244
248
|
message: 'OpenCode session retry failed',
|
|
@@ -326,9 +330,9 @@ function emitLegacyStepFinishPart({
|
|
|
326
330
|
state: TranslationState;
|
|
327
331
|
emit: Emit;
|
|
328
332
|
}): boolean {
|
|
329
|
-
const
|
|
330
|
-
if (!
|
|
331
|
-
const
|
|
333
|
+
const translated = translateLegacyStepFinishPart(part);
|
|
334
|
+
if (!translated) return false;
|
|
335
|
+
const { event, partId: id } = translated;
|
|
332
336
|
if (id) {
|
|
333
337
|
if (state.legacyStepFinishPartIds.has(id)) return true;
|
|
334
338
|
state.legacyStepFinishPartIds.add(id);
|
|
@@ -365,19 +369,12 @@ function emitLegacyToolPart({
|
|
|
365
369
|
input: unknown;
|
|
366
370
|
}) => void;
|
|
367
371
|
}): void {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const status = legacyToolPartStatus(toolPart);
|
|
372
|
+
const toolPart = legacyToolPartFromValue(part);
|
|
373
|
+
if (!toolPart) return;
|
|
374
|
+
const status = toolPart.status;
|
|
372
375
|
if (status !== 'running' && status !== 'completed' && status !== 'error') {
|
|
373
376
|
return;
|
|
374
377
|
}
|
|
375
|
-
if (
|
|
376
|
-
typeof toolPart.tool !== 'string' ||
|
|
377
|
-
typeof toolPart.callID !== 'string'
|
|
378
|
-
) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
378
|
const callID = toolPart.callID;
|
|
382
379
|
const rawToolName = toolPart.tool;
|
|
383
380
|
const toolName = toWireToolName(rawToolName);
|
|
@@ -402,8 +399,8 @@ function emitLegacyToolPart({
|
|
|
402
399
|
...nativeNameField({ nativeName: rawToolName, toolName }),
|
|
403
400
|
input: JSON.stringify(legacyToolPartInput(toolPart)),
|
|
404
401
|
providerExecuted: true,
|
|
405
|
-
...(toolPart.
|
|
406
|
-
? { providerMetadata: toolPart.
|
|
402
|
+
...(toolPart.providerMetadata
|
|
403
|
+
? { providerMetadata: toolPart.providerMetadata }
|
|
407
404
|
: {}),
|
|
408
405
|
});
|
|
409
406
|
}
|
|
@@ -422,33 +419,16 @@ function emitLegacyToolPart({
|
|
|
422
419
|
}
|
|
423
420
|
}
|
|
424
421
|
|
|
425
|
-
function
|
|
426
|
-
return typeof part.state === 'string'
|
|
427
|
-
? part.state
|
|
428
|
-
: typeof part.state === 'object' && part.state !== null
|
|
429
|
-
? String(part.state.status ?? '')
|
|
430
|
-
: undefined;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function legacyToolPartInput(
|
|
434
|
-
part: Record<string, any>,
|
|
435
|
-
): Record<string, unknown> {
|
|
436
|
-
const state =
|
|
437
|
-
typeof part.state === 'object' && part.state !== null
|
|
438
|
-
? (part.state as Record<string, any>)
|
|
439
|
-
: undefined;
|
|
422
|
+
function legacyToolPartInput(part: LegacyToolPart): Record<string, unknown> {
|
|
440
423
|
return {
|
|
441
|
-
...(
|
|
442
|
-
...(
|
|
443
|
-
...(
|
|
424
|
+
...(part.metadata ?? {}),
|
|
425
|
+
...(part.state?.metadata ?? {}),
|
|
426
|
+
...(part.state?.input ?? {}),
|
|
444
427
|
};
|
|
445
428
|
}
|
|
446
429
|
|
|
447
|
-
function legacyToolPartOutput(part:
|
|
448
|
-
const state =
|
|
449
|
-
typeof part.state === 'object' && part.state !== null
|
|
450
|
-
? (part.state as Record<string, any>)
|
|
451
|
-
: undefined;
|
|
430
|
+
function legacyToolPartOutput(part: LegacyToolPart): unknown {
|
|
431
|
+
const state = part.state;
|
|
452
432
|
if (state?.status === 'error') {
|
|
453
433
|
return state.error ?? part.error ?? state.result ?? 'tool failed';
|
|
454
434
|
}
|
|
@@ -463,7 +443,7 @@ function legacyToolPartOutput(part: Record<string, any>): unknown {
|
|
|
463
443
|
|
|
464
444
|
function parseToolInput(
|
|
465
445
|
state: TranslationState,
|
|
466
|
-
props:
|
|
446
|
+
props: OpenCodeEventProperties,
|
|
467
447
|
): unknown {
|
|
468
448
|
const text = state.toolInputs.get(String(props.callID ?? ''));
|
|
469
449
|
if (!text) return {};
|
|
@@ -487,11 +467,12 @@ function nextRetryEventMessage({
|
|
|
487
467
|
details.push(`attempt ${props.attempt}`);
|
|
488
468
|
}
|
|
489
469
|
const error = props.error;
|
|
490
|
-
|
|
470
|
+
const errorDetails = openCodeErrorFromValue(error);
|
|
471
|
+
if (errorDetails) {
|
|
491
472
|
const message =
|
|
492
|
-
stringValue(
|
|
493
|
-
|
|
494
|
-
const statusCode =
|
|
473
|
+
stringValue(errorDetails.message) ??
|
|
474
|
+
stringValue(errorDetails.data?.message);
|
|
475
|
+
const statusCode = errorDetails.statusCode;
|
|
495
476
|
if (typeof statusCode === 'number') {
|
|
496
477
|
details.push(`HTTP ${statusCode}`);
|
|
497
478
|
}
|
|
@@ -507,3 +488,87 @@ function nextRetryEventMessage({
|
|
|
507
488
|
export function stringValue(value: unknown): string | undefined {
|
|
508
489
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
509
490
|
}
|
|
491
|
+
|
|
492
|
+
type LegacyToolState = {
|
|
493
|
+
status?: string;
|
|
494
|
+
input?: Record<string, unknown>;
|
|
495
|
+
metadata?: Record<string, unknown>;
|
|
496
|
+
output?: unknown;
|
|
497
|
+
result?: unknown;
|
|
498
|
+
structured?: unknown;
|
|
499
|
+
content?: unknown;
|
|
500
|
+
error?: unknown;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
type LegacyToolPart = {
|
|
504
|
+
type: 'tool';
|
|
505
|
+
callID: string;
|
|
506
|
+
tool: string;
|
|
507
|
+
status?: string;
|
|
508
|
+
state?: LegacyToolState;
|
|
509
|
+
metadata?: Record<string, unknown>;
|
|
510
|
+
providerMetadata?: unknown;
|
|
511
|
+
error?: unknown;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
function legacyToolPartFromValue(value: unknown): LegacyToolPart | undefined {
|
|
515
|
+
const part = asOpenCodeObject(value);
|
|
516
|
+
if (
|
|
517
|
+
!part ||
|
|
518
|
+
part.type !== 'tool' ||
|
|
519
|
+
typeof part.tool !== 'string' ||
|
|
520
|
+
typeof part.callID !== 'string'
|
|
521
|
+
) {
|
|
522
|
+
return undefined;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const stateObject = asOpenCodeObject(part.state);
|
|
526
|
+
const provider = asOpenCodeObject(part.provider);
|
|
527
|
+
const state = stateObject
|
|
528
|
+
? {
|
|
529
|
+
status: stringValue(stateObject.status),
|
|
530
|
+
input: asOpenCodeObject(stateObject.input),
|
|
531
|
+
metadata: asOpenCodeObject(stateObject.metadata),
|
|
532
|
+
output: stateObject.output,
|
|
533
|
+
result: stateObject.result,
|
|
534
|
+
structured: stateObject.structured,
|
|
535
|
+
content: stateObject.content,
|
|
536
|
+
error: stateObject.error,
|
|
537
|
+
}
|
|
538
|
+
: undefined;
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
type: 'tool',
|
|
542
|
+
callID: part.callID,
|
|
543
|
+
tool: part.tool,
|
|
544
|
+
status:
|
|
545
|
+
typeof part.state === 'string'
|
|
546
|
+
? part.state
|
|
547
|
+
: (state?.status ?? undefined),
|
|
548
|
+
state,
|
|
549
|
+
metadata: asOpenCodeObject(part.metadata),
|
|
550
|
+
providerMetadata: provider?.metadata,
|
|
551
|
+
error: part.error,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
type OpenCodeErrorDetails = {
|
|
556
|
+
isRetryable?: unknown;
|
|
557
|
+
message?: unknown;
|
|
558
|
+
statusCode?: unknown;
|
|
559
|
+
data?: { message?: unknown };
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
function openCodeErrorFromValue(
|
|
563
|
+
value: unknown,
|
|
564
|
+
): OpenCodeErrorDetails | undefined {
|
|
565
|
+
const error = asOpenCodeObject(value);
|
|
566
|
+
if (!error) return undefined;
|
|
567
|
+
const data = asOpenCodeObject(error.data);
|
|
568
|
+
return {
|
|
569
|
+
isRetryable: error.isRetryable,
|
|
570
|
+
message: error.message,
|
|
571
|
+
statusCode: error.statusCode,
|
|
572
|
+
data: data ? { message: data.message } : undefined,
|
|
573
|
+
};
|
|
574
|
+
}
|
package/src/bridge/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
type BridgeEvent,
|
|
4
4
|
type BridgeTurn,
|
|
5
5
|
} from '@ai-sdk/harness/bridge';
|
|
6
|
-
import { isRecord } from '@ai-sdk/provider-utils';
|
|
7
6
|
import { randomUUID } from 'node:crypto';
|
|
8
7
|
import { mkdirSync } from 'node:fs';
|
|
9
8
|
import path from 'node:path';
|
|
@@ -19,7 +18,6 @@ import {
|
|
|
19
18
|
emitOpenCodeStreamStart,
|
|
20
19
|
getOpenCodeEventSessionId,
|
|
21
20
|
isStepSettlementEvent,
|
|
22
|
-
type OpenCodeEvent,
|
|
23
21
|
type TranslationState,
|
|
24
22
|
unwrapOpenCodeEvent,
|
|
25
23
|
} from './opencode-events';
|
|
@@ -35,6 +33,11 @@ import {
|
|
|
35
33
|
type HarnessUsage,
|
|
36
34
|
type OpenCodeTokenUsage,
|
|
37
35
|
} from './opencode-usage';
|
|
36
|
+
import {
|
|
37
|
+
asOpenCodeObject,
|
|
38
|
+
type OpenCodeEvent,
|
|
39
|
+
type OpenCodeObject,
|
|
40
|
+
} from './opencode-types';
|
|
38
41
|
import { startAuthorizedToolRelay, type ToolRelay } from './tool-relay';
|
|
39
42
|
|
|
40
43
|
type Emit = (msg: Record<string, unknown>) => void;
|
|
@@ -834,12 +837,7 @@ async function handlePermissionV2({
|
|
|
834
837
|
? props.resources.map(String)
|
|
835
838
|
: [],
|
|
836
839
|
requestID,
|
|
837
|
-
toolCallId:
|
|
838
|
-
typeof props.source === 'object' &&
|
|
839
|
-
props.source !== null &&
|
|
840
|
-
'callID' in props.source
|
|
841
|
-
? String((props.source as { callID?: unknown }).callID)
|
|
842
|
-
: requestID,
|
|
840
|
+
toolCallId: String(props.source?.callID ?? requestID),
|
|
843
841
|
permissionMode,
|
|
844
842
|
builtinToolFiltering,
|
|
845
843
|
turn,
|
|
@@ -873,16 +871,12 @@ async function handlePermission({
|
|
|
873
871
|
const props = event.properties ?? {};
|
|
874
872
|
const requestID = String(props.id ?? '');
|
|
875
873
|
if (!requestID) return;
|
|
874
|
+
const tool = asOpenCodeObject(props.tool);
|
|
876
875
|
const reply = await selectPermissionReply({
|
|
877
876
|
action: String(props.permission ?? ''),
|
|
878
877
|
resources: Array.isArray(props.patterns) ? props.patterns.map(String) : [],
|
|
879
878
|
requestID,
|
|
880
|
-
toolCallId:
|
|
881
|
-
typeof props.tool === 'object' &&
|
|
882
|
-
props.tool !== null &&
|
|
883
|
-
'callID' in props.tool
|
|
884
|
-
? String((props.tool as { callID?: unknown }).callID)
|
|
885
|
-
: requestID,
|
|
879
|
+
toolCallId: String(tool?.callID ?? requestID),
|
|
886
880
|
permissionMode,
|
|
887
881
|
builtinToolFiltering,
|
|
888
882
|
turn,
|
|
@@ -1302,15 +1296,13 @@ function modelRefFromAssistantSnapshot(
|
|
|
1302
1296
|
const direct = modelRefFromValue(assistant);
|
|
1303
1297
|
if (direct) return direct;
|
|
1304
1298
|
|
|
1305
|
-
|
|
1306
|
-
return modelRefFromValue(assistant.metadata.assistant);
|
|
1307
|
-
}
|
|
1308
|
-
return undefined;
|
|
1299
|
+
return modelRefFromValue(asOpenCodeObject(assistant.metadata)?.assistant);
|
|
1309
1300
|
}
|
|
1310
1301
|
|
|
1311
1302
|
function modelRefFromSessionInfo(data: unknown): OpenCodeModelRef | undefined {
|
|
1312
|
-
|
|
1313
|
-
|
|
1303
|
+
const session = asOpenCodeObject(data);
|
|
1304
|
+
if (!session) return undefined;
|
|
1305
|
+
return modelRefFromValue(session.model) ?? modelRefFromObject(session);
|
|
1314
1306
|
}
|
|
1315
1307
|
|
|
1316
1308
|
function modelRefFromStart(start: StartMessage): OpenCodeModelRef | undefined {
|
|
@@ -1324,7 +1316,13 @@ function modelRefFromStart(start: StartMessage): OpenCodeModelRef | undefined {
|
|
|
1324
1316
|
}
|
|
1325
1317
|
|
|
1326
1318
|
function modelRefFromValue(value: unknown): OpenCodeModelRef | undefined {
|
|
1327
|
-
|
|
1319
|
+
const model = asOpenCodeObject(value);
|
|
1320
|
+
return model ? modelRefFromObject(model) : undefined;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
function modelRefFromObject(
|
|
1324
|
+
value: OpenCodeObject,
|
|
1325
|
+
): OpenCodeModelRef | undefined {
|
|
1328
1326
|
const providerID = stringValue(value.providerID);
|
|
1329
1327
|
const modelID = stringValue(value.modelID ?? value.id);
|
|
1330
1328
|
if (!providerID || !modelID) return undefined;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
asOpenCodeObject,
|
|
3
|
+
type OpenCodeEvent,
|
|
4
|
+
type OpenCodeEventProperties,
|
|
5
|
+
type OpenCodeMessageInfo,
|
|
6
|
+
} from './opencode-types';
|
|
8
7
|
|
|
9
8
|
type Emit = (msg: Record<string, unknown>) => void;
|
|
10
9
|
|
|
@@ -53,10 +52,12 @@ export function emitOpenCodeStreamStart({
|
|
|
53
52
|
state: TranslationState;
|
|
54
53
|
emit: Emit;
|
|
55
54
|
}): void {
|
|
56
|
-
if (state.streamStarted
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
if (state.streamStarted) return;
|
|
56
|
+
const message = openCodeMessageInfoFromValue(info);
|
|
57
|
+
if (!message) return;
|
|
58
|
+
if (message.role !== 'assistant' && message.type !== 'assistant') return;
|
|
59
|
+
const providerID = stringValue(message.providerID);
|
|
60
|
+
const modelID = stringValue(message.modelID);
|
|
60
61
|
const modelId =
|
|
61
62
|
providerID && modelID ? `${providerID}/${modelID}` : undefined;
|
|
62
63
|
|
|
@@ -67,20 +68,24 @@ export function emitOpenCodeStreamStart({
|
|
|
67
68
|
export function unwrapOpenCodeEvent(
|
|
68
69
|
rawEvent: unknown,
|
|
69
70
|
): OpenCodeEvent | undefined {
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
const raw = asOpenCodeObject(rawEvent);
|
|
72
|
+
if (!raw) return undefined;
|
|
72
73
|
if (raw.type === 'sync' && raw.syncEvent) {
|
|
73
|
-
const sync = raw.syncEvent
|
|
74
|
+
const sync = asOpenCodeObject(raw.syncEvent);
|
|
75
|
+
if (!sync) return undefined;
|
|
74
76
|
return {
|
|
75
77
|
id: String(sync.id ?? raw.id ?? ''),
|
|
76
78
|
type: stripSyncVersion(String(sync.type ?? '')),
|
|
77
|
-
properties:
|
|
79
|
+
properties: openCodeEventPropertiesFromValue(sync.data) ?? {},
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
return {
|
|
81
83
|
id: typeof raw.id === 'string' ? raw.id : undefined,
|
|
82
84
|
type: typeof raw.type === 'string' ? stripSyncVersion(raw.type) : undefined,
|
|
83
|
-
properties:
|
|
85
|
+
properties:
|
|
86
|
+
openCodeEventPropertiesFromValue(raw.properties) ??
|
|
87
|
+
openCodeEventPropertiesFromValue(raw.data) ??
|
|
88
|
+
{},
|
|
84
89
|
};
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -95,14 +100,8 @@ export function getOpenCodeEventSessionId(
|
|
|
95
100
|
return props.id;
|
|
96
101
|
}
|
|
97
102
|
const part = props.part;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
typeof part === 'object' &&
|
|
101
|
-
!Array.isArray(part) &&
|
|
102
|
-
typeof (part as { sessionID?: unknown }).sessionID === 'string'
|
|
103
|
-
) {
|
|
104
|
-
return (part as { sessionID: string }).sessionID;
|
|
105
|
-
}
|
|
103
|
+
const partObject = asOpenCodeObject(part);
|
|
104
|
+
if (typeof partObject?.sessionID === 'string') return partObject.sessionID;
|
|
106
105
|
return undefined;
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -202,21 +201,21 @@ export function emitLegacyTextPartUpdate({
|
|
|
202
201
|
state: TranslationState;
|
|
203
202
|
emit: Emit;
|
|
204
203
|
}): boolean {
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
const id = stringValue(
|
|
204
|
+
const textPart = legacyTextPartFromValue(part);
|
|
205
|
+
if (!textPart) return false;
|
|
206
|
+
const id = stringValue(textPart.id);
|
|
208
207
|
if (!id) return true;
|
|
209
208
|
|
|
210
|
-
const messageID = stringValue(
|
|
209
|
+
const messageID = stringValue(textPart.messageID);
|
|
211
210
|
if (messageID && state.messageRoles.get(messageID) === 'user') return true;
|
|
212
211
|
|
|
213
|
-
const isReasoning =
|
|
212
|
+
const isReasoning = textPart.type === 'reasoning';
|
|
214
213
|
const ids = isReasoning
|
|
215
214
|
? state.legacyReasoningPartIds
|
|
216
215
|
: state.legacyTextPartIds;
|
|
217
216
|
const deltaMap = isReasoning ? state.reasoningDeltas : state.textDeltas;
|
|
218
217
|
const deltaType = isReasoning ? 'reasoning-delta' : 'text-delta';
|
|
219
|
-
const text = typeof
|
|
218
|
+
const text = typeof textPart.text === 'string' ? textPart.text : undefined;
|
|
220
219
|
|
|
221
220
|
startLegacyPart({
|
|
222
221
|
ids,
|
|
@@ -236,7 +235,7 @@ export function emitLegacyTextPartUpdate({
|
|
|
236
235
|
deltaMap.set(id, text);
|
|
237
236
|
}
|
|
238
237
|
|
|
239
|
-
if (
|
|
238
|
+
if (textPart.time?.end != null) {
|
|
240
239
|
ids.delete(id);
|
|
241
240
|
deltaMap.delete(id);
|
|
242
241
|
emit({ type: isReasoning ? 'reasoning-end' : 'text-end', id });
|
|
@@ -249,10 +248,6 @@ function stripSyncVersion(type: string): string {
|
|
|
249
248
|
return type.replace(/\.\d+$/, '');
|
|
250
249
|
}
|
|
251
250
|
|
|
252
|
-
function asRecord(value: unknown): Record<string, any> | undefined {
|
|
253
|
-
return isRecord(value) ? value : undefined;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
251
|
function stringValue(value: unknown): string | undefined {
|
|
257
252
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
258
253
|
}
|
|
@@ -283,6 +278,36 @@ function startLegacyPart({
|
|
|
283
278
|
emit({ type: `${type}-start`, id });
|
|
284
279
|
}
|
|
285
280
|
|
|
286
|
-
function
|
|
287
|
-
|
|
281
|
+
export function openCodeMessageInfoFromValue(
|
|
282
|
+
value: unknown,
|
|
283
|
+
): OpenCodeMessageInfo | undefined {
|
|
284
|
+
return asOpenCodeObject(value) as OpenCodeMessageInfo | undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function openCodeEventPropertiesFromValue(
|
|
288
|
+
value: unknown,
|
|
289
|
+
): OpenCodeEventProperties | undefined {
|
|
290
|
+
return asOpenCodeObject(value) as OpenCodeEventProperties | undefined;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
type LegacyTextPart = {
|
|
294
|
+
type: 'text' | 'reasoning';
|
|
295
|
+
id?: unknown;
|
|
296
|
+
messageID?: unknown;
|
|
297
|
+
text?: unknown;
|
|
298
|
+
time?: { end?: unknown };
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
function legacyTextPartFromValue(value: unknown): LegacyTextPart | undefined {
|
|
302
|
+
const part = asOpenCodeObject(value);
|
|
303
|
+
if (!part || (part.type !== 'text' && part.type !== 'reasoning')) {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
type: part.type,
|
|
308
|
+
id: part.id,
|
|
309
|
+
messageID: part.messageID,
|
|
310
|
+
text: part.text,
|
|
311
|
+
time: asOpenCodeObject(part.time),
|
|
312
|
+
};
|
|
288
313
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HarnessV1StreamPart } from '@ai-sdk/harness';
|
|
2
|
-
import { isRecord } from '@ai-sdk/provider-utils';
|
|
3
2
|
import { mapUsage } from './opencode-usage';
|
|
3
|
+
import { asOpenCodeObject } from './opencode-types';
|
|
4
4
|
|
|
5
5
|
type FinishStepEvent = Extract<HarnessV1StreamPart, { type: 'finish-step' }>;
|
|
6
6
|
|
|
@@ -20,9 +20,16 @@ export function mapOpenCodeFinishReason(
|
|
|
20
20
|
export function legacyStepFinishPartToFinishStep(
|
|
21
21
|
part: unknown,
|
|
22
22
|
): FinishStepEvent | undefined {
|
|
23
|
-
|
|
23
|
+
return translateLegacyStepFinishPart(part)?.event;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function translateLegacyStepFinishPart(
|
|
27
|
+
value: unknown,
|
|
28
|
+
): { event: FinishStepEvent; partId?: string } | undefined {
|
|
29
|
+
const part = asOpenCodeObject(value);
|
|
30
|
+
if (!part || part.type !== 'step-finish') return undefined;
|
|
24
31
|
const rawFinish = typeof part.reason === 'string' ? part.reason : 'stop';
|
|
25
|
-
|
|
32
|
+
const event: FinishStepEvent = {
|
|
26
33
|
type: 'finish-step',
|
|
27
34
|
finishReason: {
|
|
28
35
|
unified: mapOpenCodeFinishReason(rawFinish),
|
|
@@ -33,4 +40,8 @@ export function legacyStepFinishPartToFinishStep(
|
|
|
33
40
|
? { harnessMetadata: { opencode: { cost: part.cost } } }
|
|
34
41
|
: {}),
|
|
35
42
|
};
|
|
43
|
+
return {
|
|
44
|
+
event,
|
|
45
|
+
...(typeof part.id === 'string' ? { partId: part.id } : {}),
|
|
46
|
+
};
|
|
36
47
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type OpenCodeObject = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export type OpenCodeMessageInfo = {
|
|
4
|
+
id?: unknown;
|
|
5
|
+
role?: unknown;
|
|
6
|
+
type?: unknown;
|
|
7
|
+
providerID?: unknown;
|
|
8
|
+
modelID?: unknown;
|
|
9
|
+
tokens?: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type OpenCodeEventProperties = OpenCodeObject & {
|
|
13
|
+
info?: OpenCodeMessageInfo;
|
|
14
|
+
provider?: { metadata?: unknown };
|
|
15
|
+
source?: { callID?: unknown };
|
|
16
|
+
status?: {
|
|
17
|
+
type?: unknown;
|
|
18
|
+
attempt?: unknown;
|
|
19
|
+
message?: unknown;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type OpenCodeEvent = {
|
|
24
|
+
id?: string;
|
|
25
|
+
type?: string;
|
|
26
|
+
properties?: OpenCodeEventProperties;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function asOpenCodeObject(value: unknown): OpenCodeObject | undefined {
|
|
30
|
+
return value != null && typeof value === 'object' && !Array.isArray(value)
|
|
31
|
+
? (value as OpenCodeObject)
|
|
32
|
+
: undefined;
|
|
33
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HarnessV1StreamPart } from '@ai-sdk/harness';
|
|
2
|
-
import {
|
|
2
|
+
import { asOpenCodeObject } from './opencode-types';
|
|
3
3
|
|
|
4
4
|
type FinishStepEvent = Extract<HarnessV1StreamPart, { type: 'finish-step' }>;
|
|
5
5
|
|
|
@@ -40,13 +40,13 @@ export function defaultUsage(): HarnessUsage {
|
|
|
40
40
|
export function extractSessionTokens(
|
|
41
41
|
value: unknown,
|
|
42
42
|
): OpenCodeTokenUsage | undefined {
|
|
43
|
-
const record =
|
|
43
|
+
const record = asOpenCodeObject(value) as SessionTokenEnvelope | undefined;
|
|
44
44
|
if (!record) return undefined;
|
|
45
45
|
const tokens =
|
|
46
46
|
extractOpenCodeTokens(record.tokens) ??
|
|
47
|
-
extractOpenCodeTokens(
|
|
48
|
-
extractOpenCodeTokens(
|
|
49
|
-
extractOpenCodeTokens(
|
|
47
|
+
extractOpenCodeTokens(record.info?.tokens) ??
|
|
48
|
+
extractOpenCodeTokens(record.data?.tokens) ??
|
|
49
|
+
extractOpenCodeTokens(record.data?.data?.tokens);
|
|
50
50
|
return tokens;
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -105,8 +105,8 @@ export function addUsage({
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
function extractOpenCodeTokens(value: unknown): OpenCodeTokenUsage | undefined {
|
|
108
|
-
const record =
|
|
109
|
-
const cache =
|
|
108
|
+
const record = asOpenCodeObject(value);
|
|
109
|
+
const cache = asOpenCodeObject(record?.cache);
|
|
110
110
|
if (!record || !cache) return undefined;
|
|
111
111
|
return {
|
|
112
112
|
input: numberValue(record.input),
|
|
@@ -129,11 +129,11 @@ function zeroOpenCodeTokens(): OpenCodeTokenUsage {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function asTokenGroup(value: unknown): Record<string, number | undefined> {
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
132
|
+
return (
|
|
133
|
+
(asOpenCodeObject(value) as
|
|
134
|
+
| Record<string, number | undefined>
|
|
135
|
+
| undefined) ?? {}
|
|
136
|
+
);
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
function numberValue(value: unknown): number {
|
|
@@ -157,3 +157,12 @@ function add({
|
|
|
157
157
|
? undefined
|
|
158
158
|
: (leftNumber ?? 0) + (rightNumber ?? 0);
|
|
159
159
|
}
|
|
160
|
+
|
|
161
|
+
type SessionTokenEnvelope = {
|
|
162
|
+
tokens?: unknown;
|
|
163
|
+
info?: { tokens?: unknown };
|
|
164
|
+
data?: {
|
|
165
|
+
tokens?: unknown;
|
|
166
|
+
data?: { tokens?: unknown };
|
|
167
|
+
};
|
|
168
|
+
};
|