@jmoyers/harness 0.1.8 → 0.1.10

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.
Files changed (41) hide show
  1. package/README.md +33 -155
  2. package/package.json +5 -1
  3. package/packages/harness-ai/src/anthropic-client.ts +99 -0
  4. package/packages/harness-ai/src/anthropic-protocol.ts +581 -0
  5. package/packages/harness-ai/src/anthropic-provider.ts +82 -0
  6. package/packages/harness-ai/src/async-iterable-stream.ts +65 -0
  7. package/packages/harness-ai/src/index.ts +36 -0
  8. package/packages/harness-ai/src/json-parse.ts +66 -0
  9. package/packages/harness-ai/src/sse.ts +80 -0
  10. package/packages/harness-ai/src/stream-object.ts +96 -0
  11. package/packages/harness-ai/src/stream-text.ts +1340 -0
  12. package/packages/harness-ai/src/types.ts +330 -0
  13. package/packages/harness-ai/src/ui-stream.ts +217 -0
  14. package/scripts/codex-live-mux-runtime.ts +123 -7
  15. package/scripts/control-plane-daemon.ts +20 -3
  16. package/scripts/harness.ts +566 -133
  17. package/src/cli/gateway-record.ts +16 -1
  18. package/src/control-plane/agent-realtime-api.ts +4 -0
  19. package/src/control-plane/prompt/agent-prompt-extractor.ts +191 -0
  20. package/src/control-plane/prompt/extractors/claude-prompt-extractor.ts +53 -0
  21. package/src/control-plane/prompt/extractors/codex-prompt-extractor.ts +50 -0
  22. package/src/control-plane/prompt/extractors/cursor-prompt-extractor.ts +56 -0
  23. package/src/control-plane/prompt/session-prompt-engine.ts +69 -0
  24. package/src/control-plane/prompt/thread-title-namer.ts +290 -0
  25. package/src/control-plane/stream-command-parser.ts +12 -0
  26. package/src/control-plane/stream-protocol.ts +109 -0
  27. package/src/control-plane/stream-server-command.ts +14 -0
  28. package/src/control-plane/stream-server-session-runtime.ts +12 -0
  29. package/src/control-plane/stream-server.ts +485 -19
  30. package/src/mux/input-shortcuts.ts +9 -0
  31. package/src/mux/live-mux/critique-review.ts +5 -1
  32. package/src/mux/live-mux/git-parsing.ts +24 -0
  33. package/src/mux/live-mux/global-shortcut-handlers.ts +8 -0
  34. package/src/mux/render-frame.ts +1 -1
  35. package/src/pty/pty_host.ts +46 -1
  36. package/src/services/control-plane.ts +22 -0
  37. package/src/services/runtime-control-actions.ts +69 -0
  38. package/src/services/runtime-navigation-input.ts +4 -0
  39. package/src/services/runtime-rail-input.ts +4 -0
  40. package/src/services/runtime-workspace-actions.ts +5 -0
  41. package/src/ui/global-shortcut-input.ts +2 -0
@@ -0,0 +1,330 @@
1
+ type JsonPrimitive = string | number | boolean | null;
2
+
3
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
4
+
5
+ interface JsonObject {
6
+ readonly [key: string]: JsonValue;
7
+ }
8
+
9
+ export interface JsonSchema {
10
+ readonly [key: string]: JsonValue;
11
+ }
12
+
13
+ export type FinishReason = 'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'other';
14
+
15
+ export interface LanguageModelUsage {
16
+ readonly inputTokens: number;
17
+ readonly outputTokens: number;
18
+ readonly totalTokens: number;
19
+ readonly reasoningTokens?: number;
20
+ readonly cachedInputTokens?: number;
21
+ }
22
+
23
+ export interface LanguageModelRequestMetadata {
24
+ readonly body: Record<string, unknown>;
25
+ }
26
+
27
+ export interface LanguageModelResponseMetadata {
28
+ readonly id?: string;
29
+ readonly modelId?: string;
30
+ readonly timestamp?: Date;
31
+ readonly headers?: Headers;
32
+ }
33
+
34
+ export type ProviderMetadata = Record<string, Record<string, unknown>>;
35
+
36
+ export interface FunctionToolDefinition<INPUT = unknown, OUTPUT = unknown> {
37
+ readonly type?: 'function';
38
+ readonly description?: string;
39
+ readonly inputSchema?: JsonSchema;
40
+ readonly execute?: (input: INPUT) => Promise<OUTPUT> | OUTPUT;
41
+ readonly dynamic?: boolean;
42
+ readonly title?: string;
43
+ }
44
+
45
+ export interface AnthropicProviderToolDefinition {
46
+ readonly type: 'provider';
47
+ readonly provider: 'anthropic';
48
+ readonly anthropicType: string;
49
+ readonly name: string;
50
+ readonly description?: string;
51
+ readonly inputSchema?: JsonSchema;
52
+ readonly settings?: Record<string, JsonValue>;
53
+ readonly dynamic?: boolean;
54
+ readonly title?: string;
55
+ }
56
+
57
+ export type ToolDefinition<INPUT = unknown, OUTPUT = unknown> =
58
+ | FunctionToolDefinition<INPUT, OUTPUT>
59
+ | AnthropicProviderToolDefinition;
60
+
61
+ export type ToolSet = Record<string, ToolDefinition<unknown, unknown>>;
62
+
63
+ type ToolName<TOOLS extends ToolSet> = Extract<keyof TOOLS, string>;
64
+
65
+ export interface TypedToolCall<TOOLS extends ToolSet> {
66
+ readonly toolCallId: string;
67
+ readonly toolName: ToolName<TOOLS> | string;
68
+ readonly input: unknown;
69
+ readonly providerExecuted?: boolean;
70
+ readonly dynamic?: boolean;
71
+ readonly providerMetadata?: ProviderMetadata;
72
+ readonly title?: string;
73
+ readonly invalid?: boolean;
74
+ readonly error?: string;
75
+ }
76
+
77
+ export interface TypedToolResult<TOOLS extends ToolSet> {
78
+ readonly toolCallId: string;
79
+ readonly toolName: ToolName<TOOLS> | string;
80
+ readonly output: unknown;
81
+ readonly input?: unknown;
82
+ readonly providerExecuted?: boolean;
83
+ readonly preliminary?: boolean;
84
+ readonly dynamic?: boolean;
85
+ }
86
+
87
+ export interface TypedToolError<TOOLS extends ToolSet> {
88
+ readonly toolCallId: string;
89
+ readonly toolName: ToolName<TOOLS> | string;
90
+ readonly error: unknown;
91
+ readonly input?: unknown;
92
+ readonly providerExecuted?: boolean;
93
+ readonly dynamic?: boolean;
94
+ }
95
+
96
+ export interface TextContentPart {
97
+ readonly type: 'text';
98
+ readonly text: string;
99
+ }
100
+
101
+ export interface AssistantToolCallPart {
102
+ readonly type: 'tool-call';
103
+ readonly toolCallId: string;
104
+ readonly toolName: string;
105
+ readonly input: unknown;
106
+ }
107
+
108
+ export interface ToolResultContentPart {
109
+ readonly type: 'tool-result';
110
+ readonly toolCallId: string;
111
+ readonly toolName: string;
112
+ readonly output: unknown;
113
+ readonly isError?: boolean;
114
+ }
115
+
116
+ interface SystemModelMessage {
117
+ readonly role: 'system';
118
+ readonly content: string;
119
+ }
120
+
121
+ interface UserModelMessage {
122
+ readonly role: 'user';
123
+ readonly content: string | TextContentPart[];
124
+ }
125
+
126
+ export interface AssistantModelMessage {
127
+ readonly role: 'assistant';
128
+ readonly content: string | (TextContentPart | AssistantToolCallPart)[];
129
+ }
130
+
131
+ export interface ToolModelMessage {
132
+ readonly role: 'tool';
133
+ readonly content: ToolResultContentPart[];
134
+ }
135
+
136
+ export type ModelMessage =
137
+ | SystemModelMessage
138
+ | UserModelMessage
139
+ | AssistantModelMessage
140
+ | ToolModelMessage;
141
+
142
+ export interface HarnessAnthropicModel {
143
+ readonly provider: 'harness.anthropic';
144
+ readonly modelId: string;
145
+ readonly apiKey: string;
146
+ readonly baseUrl: string;
147
+ readonly headers: Record<string, string>;
148
+ readonly fetch: typeof fetch;
149
+ }
150
+
151
+ export type StreamTextPart<TOOLS extends ToolSet> =
152
+ | {
153
+ readonly type: 'start';
154
+ }
155
+ | {
156
+ readonly type: 'start-step';
157
+ readonly request: LanguageModelRequestMetadata;
158
+ readonly warnings: string[];
159
+ }
160
+ | {
161
+ readonly type: 'text-start';
162
+ readonly id: string;
163
+ readonly providerMetadata?: ProviderMetadata;
164
+ }
165
+ | {
166
+ readonly type: 'text-delta';
167
+ readonly id: string;
168
+ readonly text: string;
169
+ readonly providerMetadata?: ProviderMetadata;
170
+ }
171
+ | {
172
+ readonly type: 'text-end';
173
+ readonly id: string;
174
+ readonly providerMetadata?: ProviderMetadata;
175
+ }
176
+ | {
177
+ readonly type: 'reasoning-start';
178
+ readonly id: string;
179
+ readonly providerMetadata?: ProviderMetadata;
180
+ }
181
+ | {
182
+ readonly type: 'reasoning-delta';
183
+ readonly id: string;
184
+ readonly text: string;
185
+ readonly providerMetadata?: ProviderMetadata;
186
+ }
187
+ | {
188
+ readonly type: 'reasoning-end';
189
+ readonly id: string;
190
+ readonly providerMetadata?: ProviderMetadata;
191
+ }
192
+ | {
193
+ readonly type: 'tool-input-start';
194
+ readonly id: string;
195
+ readonly toolName: ToolName<TOOLS> | string;
196
+ readonly providerExecuted?: boolean;
197
+ readonly dynamic?: boolean;
198
+ readonly title?: string;
199
+ readonly providerMetadata?: ProviderMetadata;
200
+ }
201
+ | {
202
+ readonly type: 'tool-input-delta';
203
+ readonly id: string;
204
+ readonly delta: string;
205
+ readonly providerMetadata?: ProviderMetadata;
206
+ }
207
+ | {
208
+ readonly type: 'tool-input-end';
209
+ readonly id: string;
210
+ readonly providerMetadata?: ProviderMetadata;
211
+ }
212
+ | ({ readonly type: 'tool-call' } & TypedToolCall<TOOLS>)
213
+ | ({ readonly type: 'tool-result' } & TypedToolResult<TOOLS>)
214
+ | ({ readonly type: 'tool-error' } & TypedToolError<TOOLS>)
215
+ | {
216
+ readonly type: 'source';
217
+ readonly id: string;
218
+ readonly sourceType: 'url' | 'document';
219
+ readonly url?: string;
220
+ readonly title?: string;
221
+ readonly mediaType?: string;
222
+ readonly filename?: string;
223
+ readonly providerMetadata?: ProviderMetadata;
224
+ }
225
+ | {
226
+ readonly type: 'raw';
227
+ readonly rawValue: unknown;
228
+ }
229
+ | {
230
+ readonly type: 'finish-step';
231
+ readonly response: LanguageModelResponseMetadata;
232
+ readonly usage: LanguageModelUsage;
233
+ readonly finishReason: FinishReason;
234
+ readonly rawFinishReason?: string;
235
+ readonly providerMetadata?: ProviderMetadata;
236
+ }
237
+ | {
238
+ readonly type: 'finish';
239
+ readonly finishReason: FinishReason;
240
+ readonly rawFinishReason?: string;
241
+ readonly totalUsage: LanguageModelUsage;
242
+ }
243
+ | {
244
+ readonly type: 'abort';
245
+ readonly reason?: string;
246
+ }
247
+ | {
248
+ readonly type: 'error';
249
+ readonly error: unknown;
250
+ };
251
+
252
+ export interface UIMessageChunk {
253
+ readonly type:
254
+ | 'start'
255
+ | 'start-step'
256
+ | 'finish-step'
257
+ | 'abort'
258
+ | 'message-metadata'
259
+ | 'error'
260
+ | 'text-start'
261
+ | 'text-delta'
262
+ | 'text-end'
263
+ | 'reasoning-start'
264
+ | 'reasoning-delta'
265
+ | 'reasoning-end'
266
+ | 'tool-input-start'
267
+ | 'tool-input-delta'
268
+ | 'tool-input-available'
269
+ | 'tool-input-error'
270
+ | 'tool-output-available'
271
+ | 'tool-output-error'
272
+ | 'source-url'
273
+ | 'source-document'
274
+ | 'finish';
275
+ readonly [key: string]: unknown;
276
+ }
277
+
278
+ export type AsyncIterableStream<T> = ReadableStream<T> & AsyncIterable<T>;
279
+
280
+ export interface StreamTextResult<TOOLS extends ToolSet> {
281
+ readonly fullStream: AsyncIterableStream<StreamTextPart<TOOLS>>;
282
+ readonly textStream: AsyncIterableStream<string>;
283
+ readonly text: Promise<string>;
284
+ readonly toolCalls: Promise<TypedToolCall<TOOLS>[]>;
285
+ readonly toolResults: Promise<Array<TypedToolResult<TOOLS> | TypedToolError<TOOLS>>>;
286
+ readonly finishReason: Promise<FinishReason>;
287
+ readonly usage: Promise<LanguageModelUsage>;
288
+ readonly response: Promise<LanguageModelResponseMetadata>;
289
+ toUIMessageStream(): AsyncIterableStream<UIMessageChunk>;
290
+ toUIMessageStreamResponse(init?: ResponseInit): Response;
291
+ consumeStream(): Promise<void>;
292
+ }
293
+
294
+ export interface GenerateTextResult<TOOLS extends ToolSet> {
295
+ readonly text: string;
296
+ readonly finishReason: FinishReason;
297
+ readonly usage: LanguageModelUsage;
298
+ readonly response: LanguageModelResponseMetadata;
299
+ readonly toolCalls: TypedToolCall<TOOLS>[];
300
+ readonly toolResults: Array<TypedToolResult<TOOLS> | TypedToolError<TOOLS>>;
301
+ }
302
+
303
+ export interface StreamObjectResult<T> {
304
+ readonly partialObjectStream: AsyncIterableStream<Partial<T>>;
305
+ readonly object: Promise<T>;
306
+ readonly text: Promise<string>;
307
+ readonly finishReason: Promise<FinishReason>;
308
+ }
309
+
310
+ export interface StreamTextOptions<TOOLS extends ToolSet> {
311
+ readonly model: HarnessAnthropicModel;
312
+ readonly prompt?: string;
313
+ readonly messages?: ModelMessage[];
314
+ readonly system?: string;
315
+ readonly tools?: TOOLS;
316
+ readonly maxOutputTokens?: number;
317
+ readonly temperature?: number;
318
+ readonly topP?: number;
319
+ readonly stopSequences?: string[];
320
+ readonly includeRawChunks?: boolean;
321
+ readonly abortSignal?: AbortSignal;
322
+ readonly maxToolRoundtrips?: number;
323
+ }
324
+
325
+ export interface GenerateTextOptions<TOOLS extends ToolSet> extends StreamTextOptions<TOOLS> {}
326
+
327
+ export interface StreamObjectOptions<T, TOOLS extends ToolSet> extends StreamTextOptions<TOOLS> {
328
+ readonly schema: JsonSchema;
329
+ readonly validate?: (value: unknown) => value is T;
330
+ }
@@ -0,0 +1,217 @@
1
+ import type { AsyncIterableStream, StreamTextPart, ToolSet, UIMessageChunk } from './types.ts';
2
+ import { toAsyncIterableStream } from './async-iterable-stream.ts';
3
+
4
+ export const UI_MESSAGE_STREAM_HEADERS: Readonly<Record<string, string>> = {
5
+ 'content-type': 'text/event-stream',
6
+ 'cache-control': 'no-cache',
7
+ connection: 'keep-alive',
8
+ 'x-vercel-ai-ui-message-stream': 'v1',
9
+ 'x-accel-buffering': 'no',
10
+ };
11
+
12
+ export class JsonToSseTransformStream extends TransformStream<unknown, string> {
13
+ constructor() {
14
+ super({
15
+ transform(part, controller) {
16
+ controller.enqueue(`data: ${JSON.stringify(part)}\n\n`);
17
+ },
18
+ flush(controller) {
19
+ controller.enqueue('data: [DONE]\n\n');
20
+ },
21
+ });
22
+ }
23
+ }
24
+
25
+ function defaultErrorText(error: unknown): string {
26
+ if (error instanceof Error) {
27
+ return error.message;
28
+ }
29
+ return String(error);
30
+ }
31
+
32
+ export function createUIMessageStream<TOOLS extends ToolSet>(
33
+ stream: ReadableStream<StreamTextPart<TOOLS>>,
34
+ onError: (error: unknown) => string = defaultErrorText,
35
+ ): AsyncIterableStream<UIMessageChunk> {
36
+ const uiStream = stream.pipeThrough(
37
+ new TransformStream<StreamTextPart<TOOLS>, UIMessageChunk>({
38
+ transform(part, controller) {
39
+ switch (part.type) {
40
+ case 'start':
41
+ controller.enqueue({ type: 'start' });
42
+ break;
43
+ case 'start-step':
44
+ controller.enqueue({ type: 'start-step' });
45
+ break;
46
+ case 'text-start':
47
+ controller.enqueue({
48
+ type: 'text-start',
49
+ id: part.id,
50
+ providerMetadata: part.providerMetadata,
51
+ });
52
+ break;
53
+ case 'text-delta':
54
+ controller.enqueue({
55
+ type: 'text-delta',
56
+ id: part.id,
57
+ delta: part.text,
58
+ providerMetadata: part.providerMetadata,
59
+ });
60
+ break;
61
+ case 'text-end':
62
+ controller.enqueue({
63
+ type: 'text-end',
64
+ id: part.id,
65
+ providerMetadata: part.providerMetadata,
66
+ });
67
+ break;
68
+ case 'reasoning-start':
69
+ controller.enqueue({
70
+ type: 'reasoning-start',
71
+ id: part.id,
72
+ providerMetadata: part.providerMetadata,
73
+ });
74
+ break;
75
+ case 'reasoning-delta':
76
+ controller.enqueue({
77
+ type: 'reasoning-delta',
78
+ id: part.id,
79
+ delta: part.text,
80
+ providerMetadata: part.providerMetadata,
81
+ });
82
+ break;
83
+ case 'reasoning-end':
84
+ controller.enqueue({
85
+ type: 'reasoning-end',
86
+ id: part.id,
87
+ providerMetadata: part.providerMetadata,
88
+ });
89
+ break;
90
+ case 'tool-input-start':
91
+ controller.enqueue({
92
+ type: 'tool-input-start',
93
+ toolCallId: part.id,
94
+ toolName: part.toolName,
95
+ providerExecuted: part.providerExecuted,
96
+ providerMetadata: part.providerMetadata,
97
+ dynamic: part.dynamic,
98
+ title: part.title,
99
+ });
100
+ break;
101
+ case 'tool-input-delta':
102
+ controller.enqueue({
103
+ type: 'tool-input-delta',
104
+ toolCallId: part.id,
105
+ inputTextDelta: part.delta,
106
+ });
107
+ break;
108
+ case 'tool-call':
109
+ if (part.invalid) {
110
+ controller.enqueue({
111
+ type: 'tool-input-error',
112
+ toolCallId: part.toolCallId,
113
+ toolName: part.toolName,
114
+ input: part.input,
115
+ providerExecuted: part.providerExecuted,
116
+ providerMetadata: part.providerMetadata,
117
+ dynamic: part.dynamic,
118
+ title: part.title,
119
+ errorText: part.error ?? 'Invalid tool call',
120
+ });
121
+ } else {
122
+ controller.enqueue({
123
+ type: 'tool-input-available',
124
+ toolCallId: part.toolCallId,
125
+ toolName: part.toolName,
126
+ input: part.input,
127
+ providerExecuted: part.providerExecuted,
128
+ providerMetadata: part.providerMetadata,
129
+ dynamic: part.dynamic,
130
+ title: part.title,
131
+ });
132
+ }
133
+ break;
134
+ case 'tool-result':
135
+ controller.enqueue({
136
+ type: 'tool-output-available',
137
+ toolCallId: part.toolCallId,
138
+ output: part.output,
139
+ providerExecuted: part.providerExecuted,
140
+ dynamic: part.dynamic,
141
+ preliminary: part.preliminary,
142
+ });
143
+ break;
144
+ case 'tool-error':
145
+ controller.enqueue({
146
+ type: 'tool-output-error',
147
+ toolCallId: part.toolCallId,
148
+ providerExecuted: part.providerExecuted,
149
+ dynamic: part.dynamic,
150
+ errorText: onError(part.error),
151
+ });
152
+ break;
153
+ case 'source':
154
+ if (part.sourceType === 'url') {
155
+ controller.enqueue({
156
+ type: 'source-url',
157
+ sourceId: part.id,
158
+ url: part.url,
159
+ title: part.title,
160
+ providerMetadata: part.providerMetadata,
161
+ });
162
+ } else {
163
+ controller.enqueue({
164
+ type: 'source-document',
165
+ sourceId: part.id,
166
+ mediaType: part.mediaType,
167
+ title: part.title,
168
+ filename: part.filename,
169
+ providerMetadata: part.providerMetadata,
170
+ });
171
+ }
172
+ break;
173
+ case 'finish-step':
174
+ controller.enqueue({ type: 'finish-step' });
175
+ break;
176
+ case 'finish':
177
+ controller.enqueue({ type: 'finish', finishReason: part.finishReason });
178
+ break;
179
+ case 'abort':
180
+ controller.enqueue({ type: 'abort', reason: part.reason });
181
+ break;
182
+ case 'error':
183
+ controller.enqueue({ type: 'error', errorText: onError(part.error) });
184
+ break;
185
+ case 'raw':
186
+ case 'tool-input-end':
187
+ break;
188
+ default: {
189
+ const exhaustiveCheck: never = part;
190
+ throw new Error(`unhandled stream part: ${JSON.stringify(exhaustiveCheck)}`);
191
+ }
192
+ }
193
+ },
194
+ }),
195
+ );
196
+
197
+ return toAsyncIterableStream(uiStream);
198
+ }
199
+
200
+ export function createUIMessageStreamResponse(
201
+ stream: ReadableStream<UIMessageChunk>,
202
+ init?: ResponseInit,
203
+ ): Response {
204
+ const sseStream = stream
205
+ .pipeThrough(new JsonToSseTransformStream())
206
+ .pipeThrough(new TextEncoderStream());
207
+ const headers = new Headers(init?.headers ?? {});
208
+ for (const [key, value] of Object.entries(UI_MESSAGE_STREAM_HEADERS)) {
209
+ if (!headers.has(key)) {
210
+ headers.set(key, value);
211
+ }
212
+ }
213
+ return new Response(sseStream, {
214
+ ...init,
215
+ headers,
216
+ });
217
+ }