@ai-sdk/harness-claude-code 1.0.158 → 1.0.159
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 +10 -0
- package/dist/bridge/index.mjs +12 -5
- package/dist/bridge/index.mjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/bridge/index.ts +19 -5
- package/src/claude-code-bridge-protocol.ts +17 -0
- package/src/claude-code-harness.ts +110 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-claude-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.159",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
30
|
-
"@ai-sdk/provider": "4.0.
|
|
31
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.155",
|
|
30
|
+
"@ai-sdk/provider": "4.0.30",
|
|
31
|
+
"@ai-sdk/provider-utils": "5.0.63",
|
|
32
32
|
"ws": "^8.21.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
package/src/bridge/index.ts
CHANGED
|
@@ -843,7 +843,7 @@ function createQueryInput({
|
|
|
843
843
|
userMessages,
|
|
844
844
|
abortSignal,
|
|
845
845
|
}: {
|
|
846
|
-
initialUserMessage:
|
|
846
|
+
initialUserMessage: StartMessage['prompt'];
|
|
847
847
|
userMessages: Experimental_BridgeUserMessageQueue;
|
|
848
848
|
abortSignal: AbortSignal;
|
|
849
849
|
}): {
|
|
@@ -882,14 +882,28 @@ function createQueryInput({
|
|
|
882
882
|
}
|
|
883
883
|
|
|
884
884
|
const toUserMessage = (options: {
|
|
885
|
-
|
|
885
|
+
content: StartMessage['prompt'];
|
|
886
886
|
messageId: string;
|
|
887
887
|
priority?: 'next';
|
|
888
888
|
}): unknown => ({
|
|
889
889
|
type: 'user',
|
|
890
890
|
message: {
|
|
891
891
|
role: 'user',
|
|
892
|
-
content:
|
|
892
|
+
content:
|
|
893
|
+
typeof options.content === 'string'
|
|
894
|
+
? [{ type: 'text', text: options.content }]
|
|
895
|
+
: options.content.map(part =>
|
|
896
|
+
part.type === 'text'
|
|
897
|
+
? part
|
|
898
|
+
: {
|
|
899
|
+
type: 'image',
|
|
900
|
+
source: {
|
|
901
|
+
type: 'base64',
|
|
902
|
+
media_type: part.mediaType,
|
|
903
|
+
data: part.data,
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
),
|
|
893
907
|
},
|
|
894
908
|
parent_tool_use_id: null,
|
|
895
909
|
uuid: options.messageId,
|
|
@@ -963,7 +977,7 @@ function createQueryInput({
|
|
|
963
977
|
sentInitial = true;
|
|
964
978
|
return {
|
|
965
979
|
value: toUserMessage({
|
|
966
|
-
|
|
980
|
+
content: initialUserMessage,
|
|
967
981
|
messageId: initialMessageId,
|
|
968
982
|
}),
|
|
969
983
|
done: false,
|
|
@@ -983,7 +997,7 @@ function createQueryInput({
|
|
|
983
997
|
sentMessageIds.add(nextMessage.value.messageId);
|
|
984
998
|
return {
|
|
985
999
|
value: toUserMessage({
|
|
986
|
-
|
|
1000
|
+
content: nextMessage.value.text,
|
|
987
1001
|
messageId: nextMessage.value.messageId,
|
|
988
1002
|
priority: 'next',
|
|
989
1003
|
}),
|
|
@@ -30,7 +30,24 @@ const thinkingSchema = z.discriminatedUnion('type', [
|
|
|
30
30
|
z.object({ type: z.literal('disabled') }),
|
|
31
31
|
]);
|
|
32
32
|
|
|
33
|
+
const promptSchema = z.union([
|
|
34
|
+
z.string(),
|
|
35
|
+
z.array(
|
|
36
|
+
z.discriminatedUnion('type', [
|
|
37
|
+
z.object({ type: z.literal('text'), text: z.string() }),
|
|
38
|
+
z.object({
|
|
39
|
+
type: z.literal('image'),
|
|
40
|
+
data: z.string(),
|
|
41
|
+
mediaType: z.string(),
|
|
42
|
+
}),
|
|
43
|
+
]),
|
|
44
|
+
),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export type PromptContent = z.infer<typeof promptSchema>;
|
|
48
|
+
|
|
33
49
|
export const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({
|
|
50
|
+
prompt: promptSchema,
|
|
34
51
|
instructions: z.string().optional(),
|
|
35
52
|
thinking: thinkingSchema,
|
|
36
53
|
effort: z.enum(['low', 'medium', 'high', 'xhigh', 'max']).optional(),
|
|
@@ -47,10 +47,18 @@ import {
|
|
|
47
47
|
type SandboxChannelReconnectOptions,
|
|
48
48
|
} from '@ai-sdk/harness/utils';
|
|
49
49
|
import {
|
|
50
|
+
convertInlineFileDataToUint8Array,
|
|
51
|
+
convertUint8ArrayToBase64,
|
|
52
|
+
detectMediaType,
|
|
53
|
+
getTopLevelMediaType,
|
|
54
|
+
isFullMediaType,
|
|
55
|
+
isProviderReference,
|
|
50
56
|
safeParseJSON,
|
|
51
57
|
tool,
|
|
52
58
|
type Experimental_SandboxSession as SandboxSession,
|
|
53
59
|
type Experimental_SandboxProcess,
|
|
60
|
+
type FilePart,
|
|
61
|
+
type TextPart,
|
|
54
62
|
} from '@ai-sdk/provider-utils';
|
|
55
63
|
import { WebSocket } from 'ws';
|
|
56
64
|
import { z } from 'zod/v4';
|
|
@@ -72,6 +80,7 @@ import {
|
|
|
72
80
|
outboundMessageSchema,
|
|
73
81
|
type InboundMessage,
|
|
74
82
|
type OutboundMessage,
|
|
83
|
+
type PromptContent,
|
|
75
84
|
} from './claude-code-bridge-protocol';
|
|
76
85
|
import type { ClaudeCodeThinkingConfig } from './claude-code-thinking';
|
|
77
86
|
import { VERSION } from './version';
|
|
@@ -1836,7 +1845,7 @@ function createSession({
|
|
|
1836
1845
|
|
|
1837
1846
|
const startMessage = {
|
|
1838
1847
|
type: 'start' as const,
|
|
1839
|
-
prompt:
|
|
1848
|
+
prompt: convertUserPrompt(promptOpts.prompt),
|
|
1840
1849
|
tools: (promptOpts.tools ?? []).map(t => ({
|
|
1841
1850
|
name: t.name,
|
|
1842
1851
|
description: t.description,
|
|
@@ -2149,25 +2158,110 @@ function createSession({
|
|
|
2149
2158
|
}
|
|
2150
2159
|
|
|
2151
2160
|
/*
|
|
2152
|
-
* Reduce a `HarnessV1Prompt` to the
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
* them so callers learn about the
|
|
2156
|
-
* truncated prompts.
|
|
2161
|
+
* Reduce a `HarnessV1Prompt` to the prompt the bridge forwards to the Claude
|
|
2162
|
+
* SDK: plain text when every part is text, otherwise text and inline image
|
|
2163
|
+
* blocks. Remote URLs, provider references, and non-image files are not
|
|
2164
|
+
* supported: throw rather than silently drop them so callers learn about the
|
|
2165
|
+
* gap instead of seeing mysteriously truncated prompts.
|
|
2157
2166
|
*/
|
|
2158
|
-
function
|
|
2167
|
+
function convertUserPrompt(prompt: HarnessV1Prompt): PromptContent {
|
|
2159
2168
|
if (typeof prompt === 'string') return prompt;
|
|
2160
2169
|
const { content } = prompt;
|
|
2161
2170
|
if (typeof content === 'string') return content;
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2171
|
+
if (content.every((part): part is TextPart => part.type === 'text')) {
|
|
2172
|
+
return content.map(part => part.text).join('\n\n');
|
|
2173
|
+
}
|
|
2174
|
+
return content.map(part => {
|
|
2175
|
+
switch (part.type) {
|
|
2176
|
+
case 'text':
|
|
2177
|
+
return { type: 'text', text: part.text };
|
|
2178
|
+
case 'image':
|
|
2179
|
+
return convertUserImage({
|
|
2180
|
+
data: part.image,
|
|
2181
|
+
mediaType: part.mediaType ?? 'image',
|
|
2182
|
+
});
|
|
2183
|
+
case 'file':
|
|
2184
|
+
return convertUserImage(part);
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
function convertUserImage({
|
|
2190
|
+
data,
|
|
2191
|
+
mediaType,
|
|
2192
|
+
}: {
|
|
2193
|
+
data: FilePart['data'];
|
|
2194
|
+
mediaType: string;
|
|
2195
|
+
}): Exclude<PromptContent, string>[number] {
|
|
2196
|
+
if (getTopLevelMediaType(mediaType) !== 'image') {
|
|
2197
|
+
throw unsupportedUserContent(`files of media type '${mediaType}'`);
|
|
2198
|
+
}
|
|
2199
|
+
const image = getInlineImage(data);
|
|
2200
|
+
const resolvedMediaType = isFullMediaType(mediaType)
|
|
2201
|
+
? mediaType
|
|
2202
|
+
: (image.mediaType ??
|
|
2203
|
+
detectMediaType({ data: image.data, topLevelType: 'image' }));
|
|
2204
|
+
if (resolvedMediaType == null) {
|
|
2205
|
+
throw unsupportedUserContent('images whose media type cannot be detected');
|
|
2206
|
+
}
|
|
2207
|
+
return { type: 'image', data: image.data, mediaType: resolvedMediaType };
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
function getInlineImage(data: FilePart['data']): {
|
|
2211
|
+
data: string;
|
|
2212
|
+
mediaType?: string;
|
|
2213
|
+
} {
|
|
2214
|
+
if (data instanceof URL) return getDataUrlImage(data);
|
|
2215
|
+
if (typeof data === 'string') {
|
|
2216
|
+
let url: URL | undefined;
|
|
2217
|
+
try {
|
|
2218
|
+
url = new URL(data);
|
|
2219
|
+
} catch {
|
|
2220
|
+
return { data };
|
|
2169
2221
|
}
|
|
2170
|
-
|
|
2222
|
+
return getDataUrlImage(url);
|
|
2171
2223
|
}
|
|
2172
|
-
|
|
2224
|
+
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
|
2225
|
+
return {
|
|
2226
|
+
data: convertUint8ArrayToBase64(
|
|
2227
|
+
convertInlineFileDataToUint8Array({ type: 'data', data }),
|
|
2228
|
+
),
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
if (isProviderReference(data)) {
|
|
2232
|
+
throw unsupportedUserContent('provider references');
|
|
2233
|
+
}
|
|
2234
|
+
switch (data.type) {
|
|
2235
|
+
case 'data':
|
|
2236
|
+
return typeof data.data === 'string'
|
|
2237
|
+
? { data: data.data }
|
|
2238
|
+
: getInlineImage(data.data);
|
|
2239
|
+
case 'url':
|
|
2240
|
+
return getDataUrlImage(data.url);
|
|
2241
|
+
case 'reference':
|
|
2242
|
+
throw unsupportedUserContent('provider references');
|
|
2243
|
+
case 'text':
|
|
2244
|
+
throw unsupportedUserContent('text data for images');
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
function getDataUrlImage(url: URL): { data: string; mediaType?: string } {
|
|
2249
|
+
if (url.protocol !== 'data:') {
|
|
2250
|
+
throw unsupportedUserContent('remote image URLs');
|
|
2251
|
+
}
|
|
2252
|
+
const [header, data] = url.href.split(',', 2);
|
|
2253
|
+
if (!header.endsWith(';base64') || data == null) {
|
|
2254
|
+
throw unsupportedUserContent('data URLs that are not base64 encoded');
|
|
2255
|
+
}
|
|
2256
|
+
const mediaType = header.slice('data:'.length).split(';')[0];
|
|
2257
|
+
return { data, ...(mediaType ? { mediaType } : {}) };
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
function unsupportedUserContent(
|
|
2261
|
+
description: string,
|
|
2262
|
+
): HarnessCapabilityUnsupportedError {
|
|
2263
|
+
return new HarnessCapabilityUnsupportedError({
|
|
2264
|
+
harnessId: 'claude-code',
|
|
2265
|
+
message: `The claude-code harness does not support ${description} in user messages. Pass text parts and inline images as bytes, base64 strings, or data URLs.`,
|
|
2266
|
+
});
|
|
2173
2267
|
}
|