@jmoyers/harness 0.1.9 → 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 (32) hide show
  1. package/README.md +33 -156
  2. package/package.json +3 -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 +103 -3
  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/prompt/thread-title-namer.ts +290 -0
  19. package/src/control-plane/stream-command-parser.ts +12 -0
  20. package/src/control-plane/stream-protocol.ts +6 -0
  21. package/src/control-plane/stream-server-command.ts +14 -0
  22. package/src/control-plane/stream-server.ts +382 -19
  23. package/src/mux/input-shortcuts.ts +9 -0
  24. package/src/mux/live-mux/git-parsing.ts +24 -0
  25. package/src/mux/live-mux/global-shortcut-handlers.ts +8 -0
  26. package/src/mux/render-frame.ts +1 -1
  27. package/src/services/control-plane.ts +22 -0
  28. package/src/services/runtime-control-actions.ts +69 -0
  29. package/src/services/runtime-navigation-input.ts +4 -0
  30. package/src/services/runtime-rail-input.ts +4 -0
  31. package/src/services/runtime-workspace-actions.ts +5 -0
  32. package/src/ui/global-shortcut-input.ts +2 -0
@@ -0,0 +1,581 @@
1
+ import type { FinishReason } from './types.ts';
2
+
3
+ export interface AnthropicUsage {
4
+ readonly input_tokens?: number;
5
+ readonly output_tokens?: number;
6
+ readonly cache_read_input_tokens?: number;
7
+ readonly cache_creation_input_tokens?: number;
8
+ }
9
+
10
+ interface AnthropicToolUseContentBlock {
11
+ readonly type: 'tool_use' | 'server_tool_use';
12
+ readonly id: string;
13
+ readonly name: string;
14
+ readonly input?: Record<string, unknown>;
15
+ }
16
+
17
+ interface AnthropicTextContentBlock {
18
+ readonly type: 'text';
19
+ readonly text?: string;
20
+ }
21
+
22
+ interface AnthropicThinkingContentBlock {
23
+ readonly type: 'thinking' | 'redacted_thinking';
24
+ readonly thinking?: string;
25
+ readonly data?: string;
26
+ }
27
+
28
+ interface AnthropicWebSearchResultContentBlock {
29
+ readonly type: 'web_search_tool_result';
30
+ readonly tool_use_id: string;
31
+ readonly content:
32
+ | Array<{
33
+ readonly type: string;
34
+ readonly url: string;
35
+ readonly title?: string;
36
+ readonly page_age?: string;
37
+ readonly encrypted_content?: string;
38
+ }>
39
+ | {
40
+ readonly type: string;
41
+ readonly error_code?: string;
42
+ };
43
+ }
44
+
45
+ interface AnthropicWebFetchResultContentBlock {
46
+ readonly type: 'web_fetch_tool_result';
47
+ readonly tool_use_id: string;
48
+ readonly content:
49
+ | {
50
+ readonly type: 'web_fetch_result';
51
+ readonly url: string;
52
+ readonly retrieved_at?: string;
53
+ readonly content: {
54
+ readonly type: string;
55
+ readonly title?: string;
56
+ readonly source: {
57
+ readonly type: string;
58
+ readonly media_type: string;
59
+ readonly data: string;
60
+ };
61
+ readonly citations?: unknown[];
62
+ };
63
+ }
64
+ | {
65
+ readonly type: Exclude<string, 'web_fetch_result'>;
66
+ readonly error_code?: string;
67
+ };
68
+ }
69
+
70
+ export type AnthropicContentBlock =
71
+ | AnthropicToolUseContentBlock
72
+ | AnthropicTextContentBlock
73
+ | AnthropicThinkingContentBlock
74
+ | AnthropicWebSearchResultContentBlock
75
+ | AnthropicWebFetchResultContentBlock;
76
+
77
+ interface AnthropicMessageStartChunk {
78
+ readonly type: 'message_start';
79
+ readonly message: {
80
+ readonly id?: string;
81
+ readonly model?: string;
82
+ readonly usage?: AnthropicUsage;
83
+ readonly stop_reason?: string | null;
84
+ readonly content?: AnthropicContentBlock[];
85
+ };
86
+ }
87
+
88
+ interface AnthropicMessageDeltaChunk {
89
+ readonly type: 'message_delta';
90
+ readonly usage?: AnthropicUsage;
91
+ readonly delta?: {
92
+ readonly stop_reason?: string | null;
93
+ readonly stop_sequence?: string | null;
94
+ };
95
+ }
96
+
97
+ interface AnthropicMessageStopChunk {
98
+ readonly type: 'message_stop';
99
+ }
100
+
101
+ interface AnthropicContentBlockStartChunk {
102
+ readonly type: 'content_block_start';
103
+ readonly index: number;
104
+ readonly content_block: AnthropicContentBlock;
105
+ }
106
+
107
+ interface AnthropicContentBlockDeltaChunk {
108
+ readonly type: 'content_block_delta';
109
+ readonly index: number;
110
+ readonly delta:
111
+ | {
112
+ readonly type: 'text_delta';
113
+ readonly text: string;
114
+ }
115
+ | {
116
+ readonly type: 'thinking_delta';
117
+ readonly thinking: string;
118
+ }
119
+ | {
120
+ readonly type: 'signature_delta';
121
+ readonly signature: string;
122
+ }
123
+ | {
124
+ readonly type: 'input_json_delta';
125
+ readonly partial_json: string;
126
+ };
127
+ }
128
+
129
+ interface AnthropicContentBlockStopChunk {
130
+ readonly type: 'content_block_stop';
131
+ readonly index: number;
132
+ }
133
+
134
+ interface AnthropicErrorChunk {
135
+ readonly type: 'error';
136
+ readonly error: Record<string, unknown>;
137
+ }
138
+
139
+ interface AnthropicPingChunk {
140
+ readonly type: 'ping';
141
+ }
142
+
143
+ export type AnthropicStreamChunk =
144
+ | AnthropicMessageStartChunk
145
+ | AnthropicMessageDeltaChunk
146
+ | AnthropicMessageStopChunk
147
+ | AnthropicContentBlockStartChunk
148
+ | AnthropicContentBlockDeltaChunk
149
+ | AnthropicContentBlockStopChunk
150
+ | AnthropicErrorChunk
151
+ | AnthropicPingChunk;
152
+
153
+ function asRecord(value: unknown): Record<string, unknown> | null {
154
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
155
+ return null;
156
+ }
157
+ return value as Record<string, unknown>;
158
+ }
159
+
160
+ function asString(value: unknown): string | null {
161
+ return typeof value === 'string' ? value : null;
162
+ }
163
+
164
+ function asNumber(value: unknown): number | null {
165
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
166
+ }
167
+
168
+ function parseStringOrNull(value: unknown): string | null | undefined {
169
+ if (value === null) {
170
+ return null;
171
+ }
172
+ const parsed = asString(value);
173
+ return parsed === null ? undefined : parsed;
174
+ }
175
+
176
+ function parseUsage(value: unknown): AnthropicUsage | undefined {
177
+ const record = asRecord(value);
178
+ if (record === null) {
179
+ return undefined;
180
+ }
181
+
182
+ const usage: {
183
+ input_tokens?: number;
184
+ output_tokens?: number;
185
+ cache_read_input_tokens?: number;
186
+ cache_creation_input_tokens?: number;
187
+ } = {};
188
+
189
+ const inputTokens = asNumber(record['input_tokens']);
190
+ if (inputTokens !== null) {
191
+ usage.input_tokens = inputTokens;
192
+ }
193
+
194
+ const outputTokens = asNumber(record['output_tokens']);
195
+ if (outputTokens !== null) {
196
+ usage.output_tokens = outputTokens;
197
+ }
198
+
199
+ const cacheReadInputTokens = asNumber(record['cache_read_input_tokens']);
200
+ if (cacheReadInputTokens !== null) {
201
+ usage.cache_read_input_tokens = cacheReadInputTokens;
202
+ }
203
+
204
+ const cacheCreationInputTokens = asNumber(record['cache_creation_input_tokens']);
205
+ if (cacheCreationInputTokens !== null) {
206
+ usage.cache_creation_input_tokens = cacheCreationInputTokens;
207
+ }
208
+
209
+ return Object.keys(usage).length > 0 ? usage : undefined;
210
+ }
211
+
212
+ function parseContentBlock(value: unknown): AnthropicContentBlock | null {
213
+ const record = asRecord(value);
214
+ if (record === null) {
215
+ return null;
216
+ }
217
+
218
+ const type = asString(record['type']);
219
+ if (type === null) {
220
+ return null;
221
+ }
222
+
223
+ if (type === 'text') {
224
+ const text = asString(record['text']);
225
+ return text === null ? { type } : { type, text };
226
+ }
227
+
228
+ if (type === 'thinking' || type === 'redacted_thinking') {
229
+ const thinking = asString(record['thinking']);
230
+ const data = asString(record['data']);
231
+ return {
232
+ type,
233
+ ...(thinking !== null ? { thinking } : {}),
234
+ ...(data !== null ? { data } : {}),
235
+ };
236
+ }
237
+
238
+ if (type === 'tool_use' || type === 'server_tool_use') {
239
+ const id = asString(record['id']);
240
+ const name = asString(record['name']);
241
+ if (id === null || name === null) {
242
+ return null;
243
+ }
244
+
245
+ const inputRecord = asRecord(record['input']);
246
+ return inputRecord === null ? { type, id, name } : { type, id, name, input: inputRecord };
247
+ }
248
+
249
+ if (type === 'web_search_tool_result') {
250
+ const toolUseId = asString(record['tool_use_id']);
251
+ if (toolUseId === null) {
252
+ return null;
253
+ }
254
+
255
+ const content = record['content'];
256
+ if (Array.isArray(content)) {
257
+ const parsedItems = content
258
+ .map((item) => {
259
+ const itemRecord = asRecord(item);
260
+ if (itemRecord === null) {
261
+ return null;
262
+ }
263
+
264
+ const itemType = asString(itemRecord['type']);
265
+ const url = asString(itemRecord['url']);
266
+ if (itemType === null || url === null) {
267
+ return null;
268
+ }
269
+
270
+ const title = asString(itemRecord['title']);
271
+ const pageAge = asString(itemRecord['page_age']);
272
+ const encryptedContent = asString(itemRecord['encrypted_content']);
273
+ return {
274
+ type: itemType,
275
+ url,
276
+ ...(title !== null ? { title } : {}),
277
+ ...(pageAge !== null ? { page_age: pageAge } : {}),
278
+ ...(encryptedContent !== null ? { encrypted_content: encryptedContent } : {}),
279
+ };
280
+ })
281
+ .filter((item): item is NonNullable<typeof item> => item !== null);
282
+
283
+ return {
284
+ type,
285
+ tool_use_id: toolUseId,
286
+ content: parsedItems,
287
+ };
288
+ }
289
+
290
+ const contentRecord = asRecord(content);
291
+ if (contentRecord === null) {
292
+ return null;
293
+ }
294
+
295
+ const contentType = asString(contentRecord['type']);
296
+ if (contentType === null) {
297
+ return null;
298
+ }
299
+
300
+ const errorCode = asString(contentRecord['error_code']);
301
+ return {
302
+ type,
303
+ tool_use_id: toolUseId,
304
+ content: {
305
+ type: contentType,
306
+ ...(errorCode !== null ? { error_code: errorCode } : {}),
307
+ },
308
+ };
309
+ }
310
+
311
+ if (type === 'web_fetch_tool_result') {
312
+ const toolUseId = asString(record['tool_use_id']);
313
+ const contentRecord = asRecord(record['content']);
314
+ if (toolUseId === null || contentRecord === null) {
315
+ return null;
316
+ }
317
+
318
+ const contentType = asString(contentRecord['type']);
319
+ if (contentType === null) {
320
+ return null;
321
+ }
322
+
323
+ if (contentType === 'web_fetch_result') {
324
+ const innerContent = asRecord(contentRecord['content']);
325
+ const source = innerContent === null ? null : asRecord(innerContent['source']);
326
+ if (innerContent === null || source === null) {
327
+ return null;
328
+ }
329
+
330
+ const url = asString(contentRecord['url']);
331
+ const sourceType = asString(source['type']);
332
+ const sourceMediaType = asString(source['media_type']);
333
+ const sourceData = asString(source['data']);
334
+ const contentBlockType = asString(innerContent['type']);
335
+ if (
336
+ url === null ||
337
+ sourceType === null ||
338
+ sourceMediaType === null ||
339
+ sourceData === null ||
340
+ contentBlockType === null
341
+ ) {
342
+ return null;
343
+ }
344
+
345
+ const retrievedAt = asString(contentRecord['retrieved_at']);
346
+ const title = asString(innerContent['title']);
347
+ const citations = Array.isArray(innerContent['citations'])
348
+ ? (innerContent['citations'] as unknown[])
349
+ : null;
350
+
351
+ return {
352
+ type,
353
+ tool_use_id: toolUseId,
354
+ content: {
355
+ type: 'web_fetch_result',
356
+ url,
357
+ ...(retrievedAt !== null ? { retrieved_at: retrievedAt } : {}),
358
+ content: {
359
+ type: contentBlockType,
360
+ ...(title !== null ? { title } : {}),
361
+ source: {
362
+ type: sourceType,
363
+ media_type: sourceMediaType,
364
+ data: sourceData,
365
+ },
366
+ ...(citations !== null ? { citations } : {}),
367
+ },
368
+ },
369
+ };
370
+ }
371
+
372
+ const errorCode = asString(contentRecord['error_code']);
373
+ return {
374
+ type,
375
+ tool_use_id: toolUseId,
376
+ content: {
377
+ type: contentType,
378
+ ...(errorCode !== null ? { error_code: errorCode } : {}),
379
+ },
380
+ };
381
+ }
382
+
383
+ return null;
384
+ }
385
+
386
+ export function parseAnthropicStreamChunk(value: unknown): AnthropicStreamChunk | null {
387
+ const record = asRecord(value);
388
+ if (record === null) {
389
+ return null;
390
+ }
391
+
392
+ const type = asString(record['type']);
393
+ if (type === null) {
394
+ return null;
395
+ }
396
+
397
+ if (type === 'ping') {
398
+ return { type };
399
+ }
400
+
401
+ if (type === 'message_start') {
402
+ const message = asRecord(record['message']);
403
+ if (message === null) {
404
+ return null;
405
+ }
406
+
407
+ const content = Array.isArray(message['content'])
408
+ ? message['content']
409
+ .map((part) => parseContentBlock(part))
410
+ .filter((part): part is AnthropicContentBlock => part !== null)
411
+ : undefined;
412
+
413
+ const id = asString(message['id']);
414
+ const model = asString(message['model']);
415
+ const usage = parseUsage(message['usage']);
416
+ const stopReason = parseStringOrNull(message['stop_reason']);
417
+
418
+ return {
419
+ type,
420
+ message: {
421
+ ...(id !== null ? { id } : {}),
422
+ ...(model !== null ? { model } : {}),
423
+ ...(usage !== undefined ? { usage } : {}),
424
+ ...(stopReason !== undefined ? { stop_reason: stopReason } : {}),
425
+ ...(content !== undefined ? { content } : {}),
426
+ },
427
+ };
428
+ }
429
+
430
+ if (type === 'message_delta') {
431
+ const usage = parseUsage(record['usage']);
432
+ const delta = asRecord(record['delta']);
433
+
434
+ let parsedDelta: AnthropicMessageDeltaChunk['delta'] | undefined;
435
+ if (delta !== null) {
436
+ const stopReason = parseStringOrNull(delta['stop_reason']);
437
+ const stopSequence = parseStringOrNull(delta['stop_sequence']);
438
+ if (stopReason !== undefined || stopSequence !== undefined) {
439
+ parsedDelta = {
440
+ ...(stopReason !== undefined ? { stop_reason: stopReason } : {}),
441
+ ...(stopSequence !== undefined ? { stop_sequence: stopSequence } : {}),
442
+ };
443
+ }
444
+ }
445
+
446
+ return {
447
+ type,
448
+ ...(usage !== undefined ? { usage } : {}),
449
+ ...(parsedDelta !== undefined ? { delta: parsedDelta } : {}),
450
+ };
451
+ }
452
+
453
+ if (type === 'message_stop') {
454
+ return { type };
455
+ }
456
+
457
+ if (type === 'content_block_start') {
458
+ const index = asNumber(record['index']);
459
+ const contentBlock = parseContentBlock(record['content_block']);
460
+ if (index === null || contentBlock === null) {
461
+ return null;
462
+ }
463
+
464
+ return {
465
+ type,
466
+ index,
467
+ content_block: contentBlock,
468
+ };
469
+ }
470
+
471
+ if (type === 'content_block_delta') {
472
+ const index = asNumber(record['index']);
473
+ const delta = asRecord(record['delta']);
474
+ if (index === null || delta === null) {
475
+ return null;
476
+ }
477
+
478
+ const deltaType = asString(delta['type']);
479
+ if (deltaType === 'text_delta') {
480
+ const text = asString(delta['text']);
481
+ if (text === null) {
482
+ return null;
483
+ }
484
+ return {
485
+ type,
486
+ index,
487
+ delta: {
488
+ type: deltaType,
489
+ text,
490
+ },
491
+ };
492
+ }
493
+
494
+ if (deltaType === 'thinking_delta') {
495
+ const thinking = asString(delta['thinking']);
496
+ if (thinking === null) {
497
+ return null;
498
+ }
499
+ return {
500
+ type,
501
+ index,
502
+ delta: {
503
+ type: deltaType,
504
+ thinking,
505
+ },
506
+ };
507
+ }
508
+
509
+ if (deltaType === 'signature_delta') {
510
+ const signature = asString(delta['signature']);
511
+ if (signature === null) {
512
+ return null;
513
+ }
514
+ return {
515
+ type,
516
+ index,
517
+ delta: {
518
+ type: deltaType,
519
+ signature,
520
+ },
521
+ };
522
+ }
523
+
524
+ if (deltaType === 'input_json_delta') {
525
+ const partialJson = asString(delta['partial_json']);
526
+ if (partialJson === null) {
527
+ return null;
528
+ }
529
+ return {
530
+ type,
531
+ index,
532
+ delta: {
533
+ type: deltaType,
534
+ partial_json: partialJson,
535
+ },
536
+ };
537
+ }
538
+
539
+ return null;
540
+ }
541
+
542
+ if (type === 'content_block_stop') {
543
+ const index = asNumber(record['index']);
544
+ if (index === null) {
545
+ return null;
546
+ }
547
+ return {
548
+ type,
549
+ index,
550
+ };
551
+ }
552
+
553
+ if (type === 'error') {
554
+ const errorRecord = asRecord(record['error']);
555
+ if (errorRecord === null) {
556
+ return null;
557
+ }
558
+ return {
559
+ type,
560
+ error: errorRecord,
561
+ };
562
+ }
563
+
564
+ return null;
565
+ }
566
+
567
+ export function mapAnthropicStopReason(reason: string | null | undefined): FinishReason {
568
+ if (reason === 'end_turn') {
569
+ return 'stop';
570
+ }
571
+ if (reason === 'max_tokens') {
572
+ return 'length';
573
+ }
574
+ if (reason === 'tool_use') {
575
+ return 'tool-calls';
576
+ }
577
+ if (reason === 'stop_sequence') {
578
+ return 'stop';
579
+ }
580
+ return 'other';
581
+ }
@@ -0,0 +1,82 @@
1
+ import type { AnthropicProviderToolDefinition, HarnessAnthropicModel, JsonValue } from './types.ts';
2
+
3
+ export interface CreateAnthropicOptions {
4
+ readonly apiKey: string;
5
+ readonly baseUrl?: string;
6
+ readonly headers?: Record<string, string>;
7
+ readonly fetch?: typeof fetch;
8
+ }
9
+
10
+ export type AnthropicModelFactory = ((modelId: string) => HarnessAnthropicModel) & {
11
+ readonly tools: typeof anthropicTools;
12
+ };
13
+
14
+ function normalizeBaseUrl(value: string | undefined): string {
15
+ const base = value?.trim() || 'https://api.anthropic.com/v1';
16
+ return base.endsWith('/') ? base.slice(0, -1) : base;
17
+ }
18
+
19
+ function createProviderTool(
20
+ anthropicType: string,
21
+ name: string,
22
+ settings?: Record<string, JsonValue>,
23
+ ): AnthropicProviderToolDefinition {
24
+ const tool: AnthropicProviderToolDefinition = {
25
+ type: 'provider',
26
+ provider: 'anthropic',
27
+ anthropicType,
28
+ name,
29
+ };
30
+ return settings === undefined ? tool : { ...tool, settings };
31
+ }
32
+
33
+ export const anthropicTools = {
34
+ webSearch_20250305(settings?: Record<string, JsonValue>): AnthropicProviderToolDefinition {
35
+ return createProviderTool('web_search_20250305', 'web_search', settings);
36
+ },
37
+ webFetch_20250910(settings?: Record<string, JsonValue>): AnthropicProviderToolDefinition {
38
+ return createProviderTool('web_fetch_20250910', 'web_fetch', settings);
39
+ },
40
+ toolSearchRegex_20251119(settings?: Record<string, JsonValue>): AnthropicProviderToolDefinition {
41
+ return createProviderTool('tool_search_tool_regex_20251119', 'tool_search', settings);
42
+ },
43
+ toolSearchBm25_20251119(settings?: Record<string, JsonValue>): AnthropicProviderToolDefinition {
44
+ return createProviderTool('tool_search_tool_bm25_20251119', 'tool_search', settings);
45
+ },
46
+ };
47
+
48
+ export const anthropic = {
49
+ tools: anthropicTools,
50
+ } as const;
51
+
52
+ export function createAnthropic(options: CreateAnthropicOptions): AnthropicModelFactory {
53
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
54
+ const defaultHeaders = {
55
+ ...(options.headers ?? {}),
56
+ };
57
+ const runtimeFetch = options.fetch ?? fetch;
58
+
59
+ const factory = ((modelId: string): HarnessAnthropicModel => {
60
+ if (modelId.trim().length === 0) {
61
+ throw new Error('modelId is required');
62
+ }
63
+
64
+ return {
65
+ provider: 'harness.anthropic',
66
+ modelId,
67
+ apiKey: options.apiKey,
68
+ baseUrl,
69
+ headers: defaultHeaders,
70
+ fetch: runtimeFetch,
71
+ };
72
+ }) as AnthropicModelFactory;
73
+
74
+ Object.defineProperty(factory, 'tools', {
75
+ configurable: false,
76
+ enumerable: true,
77
+ writable: false,
78
+ value: anthropicTools,
79
+ });
80
+
81
+ return factory;
82
+ }