@bluecopa/harness 0.1.0-snapshot.37 → 0.1.0-snapshot.38
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/package.json
CHANGED
package/src/arc/agent-runner.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { generateText, generateObject } from 'ai';
|
|
3
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';
|
|
4
|
+
import type { ModelFactory } from './types';
|
|
4
5
|
import type { AgentMessage, ToolCallAction } from '../agent/types';
|
|
5
6
|
import { getTextContent } from '../agent/types';
|
|
6
7
|
import type { ToolProvider, ToolResult } from '../interfaces/tool-provider';
|
|
@@ -183,6 +184,7 @@ async function executeTool(
|
|
|
183
184
|
|
|
184
185
|
export interface AgentRunnerConfig {
|
|
185
186
|
model: string;
|
|
187
|
+
createModel?: ModelFactory;
|
|
186
188
|
prompt: string;
|
|
187
189
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
188
190
|
tools: Record<string, any>;
|
|
@@ -230,7 +232,6 @@ export class AgentRunner {
|
|
|
230
232
|
const cachedSystem = [{
|
|
231
233
|
role: 'system' as const,
|
|
232
234
|
content: config.systemPrompt,
|
|
233
|
-
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } },
|
|
234
235
|
}];
|
|
235
236
|
|
|
236
237
|
for (let step = 0; step < config.maxSteps; step++) {
|
|
@@ -245,8 +246,7 @@ export class AgentRunner {
|
|
|
245
246
|
const callLLM = async (effectiveSignal: AbortSignal) =>
|
|
246
247
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
248
|
(generateText as any)({
|
|
248
|
-
model:
|
|
249
|
-
thinking: { type: 'adaptive' },
|
|
249
|
+
model: (config.createModel ?? defaultAnthropicProvider)(config.model),
|
|
250
250
|
tools: config.tools,
|
|
251
251
|
toolChoice: 'auto',
|
|
252
252
|
messages: toModelMessages(messages),
|
|
@@ -289,7 +289,7 @@ export class AgentRunner {
|
|
|
289
289
|
];
|
|
290
290
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
291
291
|
const structured = await (generateObject as any)({
|
|
292
|
-
model:
|
|
292
|
+
model: (config.createModel ?? defaultAnthropicProvider)(config.model),
|
|
293
293
|
schema: config.outputSchema,
|
|
294
294
|
messages: toModelMessages(extractionMessages),
|
|
295
295
|
system: config.systemPrompt,
|
|
@@ -400,6 +400,7 @@ export class AgentRunner {
|
|
|
400
400
|
// ── createProcess factory ──
|
|
401
401
|
|
|
402
402
|
export interface CreateProcessConfig {
|
|
403
|
+
createModel?: ModelFactory;
|
|
403
404
|
toolProvider: ToolProvider;
|
|
404
405
|
episodeStore: EpisodeStore;
|
|
405
406
|
taskId: string;
|
|
@@ -449,7 +450,7 @@ export function createProcess(
|
|
|
449
450
|
|
|
450
451
|
const outbox = createChannel<ProcessEvent>();
|
|
451
452
|
|
|
452
|
-
const compressor = new EpisodeCompressor();
|
|
453
|
+
const compressor = new EpisodeCompressor(config.createModel);
|
|
453
454
|
const runner = new AgentRunner();
|
|
454
455
|
|
|
455
456
|
const process: Process = {
|
|
@@ -504,6 +505,7 @@ export function createProcess(
|
|
|
504
505
|
outbox.push({ type: 'activity', activity });
|
|
505
506
|
},
|
|
506
507
|
...pickDefined(config, [
|
|
508
|
+
'createModel',
|
|
507
509
|
'hookRunner',
|
|
508
510
|
'permissionManager',
|
|
509
511
|
'telemetry',
|
package/src/arc/arc-loop.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { streamText } from 'ai';
|
|
3
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';
|
|
4
|
+
import type { ModelFactory } from './types';
|
|
4
5
|
import type { AgentMessage, ToolCallAction } from '../agent/types';
|
|
5
6
|
import { getTextContent } from '../agent/types';
|
|
6
7
|
import type { Episode, AnyTool, ModelTier } from './arc-types';
|
|
@@ -83,9 +84,11 @@ export class ArcLoop {
|
|
|
83
84
|
private readonly actionIndex = new Map<string, string>();
|
|
84
85
|
private readonly processListeners: Promise<void>[] = [];
|
|
85
86
|
private readonly skillResolver: SkillResolver | undefined;
|
|
87
|
+
private readonly createModel: ModelFactory;
|
|
86
88
|
|
|
87
89
|
constructor(config: ArcLoopConfig) {
|
|
88
90
|
this.config = config;
|
|
91
|
+
this.createModel = config.createModel ?? defaultAnthropicProvider;
|
|
89
92
|
this.modelMap = { ...DEFAULT_MODEL_MAP, ...config.modelMap };
|
|
90
93
|
this.orchestratorModel = resolveModel(config.model, this.modelMap, this.modelMap.strong);
|
|
91
94
|
this.systemPrompt = config.systemPrompt ?? DEFAULT_ORCHESTRATOR_PROMPT;
|
|
@@ -93,7 +96,6 @@ export class ArcLoop {
|
|
|
93
96
|
this.cachedSystem = [{
|
|
94
97
|
role: 'system' as const,
|
|
95
98
|
content: this.systemPrompt,
|
|
96
|
-
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } },
|
|
97
99
|
}];
|
|
98
100
|
|
|
99
101
|
this.tools = {
|
|
@@ -118,6 +120,7 @@ export class ArcLoop {
|
|
|
118
120
|
episodeStore: config.episodeStore,
|
|
119
121
|
memory: this.memory,
|
|
120
122
|
taskId: config.taskId,
|
|
123
|
+
createModel: this.createModel,
|
|
121
124
|
});
|
|
122
125
|
|
|
123
126
|
this.resilience = config.resilience;
|
|
@@ -167,8 +170,7 @@ export class ArcLoop {
|
|
|
167
170
|
const callLLM = async (effectiveSignal: AbortSignal) =>
|
|
168
171
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
172
|
(streamText as any)({
|
|
170
|
-
model:
|
|
171
|
-
thinking: { type: 'adaptive' },
|
|
173
|
+
model: this.createModel(this.orchestratorModel),
|
|
172
174
|
tools: this.tools,
|
|
173
175
|
toolChoice: 'auto',
|
|
174
176
|
messages: toModelMessages(prepared.messages),
|
|
@@ -570,6 +572,7 @@ export class ArcLoop {
|
|
|
570
572
|
episodeStore: this.config.episodeStore,
|
|
571
573
|
taskId: this.config.taskId,
|
|
572
574
|
sessionId: this.config.sessionId,
|
|
575
|
+
createModel: this.createModel,
|
|
573
576
|
modelMap: this.modelMap,
|
|
574
577
|
defaultModel,
|
|
575
578
|
processMaxSteps: profile?.maxSteps ?? this.config.processMaxSteps ?? 20,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { generateText } from 'ai';
|
|
2
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
2
|
+
import { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';
|
|
3
|
+
import type { ModelFactory } from './types';
|
|
3
4
|
import type { AgentMessage } from '../agent/types';
|
|
4
5
|
import { getTextContent } from '../agent/types';
|
|
5
6
|
import type { Episode, EpisodeStore } from './arc-types';
|
|
@@ -17,6 +18,7 @@ export interface ContextWindowConfig {
|
|
|
17
18
|
episodeStore: EpisodeStore;
|
|
18
19
|
memory: MemoryManager;
|
|
19
20
|
taskId: string;
|
|
21
|
+
createModel?: ModelFactory;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export class ContextWindow {
|
|
@@ -249,7 +251,7 @@ export class ContextWindow {
|
|
|
249
251
|
|
|
250
252
|
const fastModel = resolveModel('fast', DEFAULT_MODEL_MAP, DEFAULT_MODEL_MAP.fast);
|
|
251
253
|
const result = await generateText({
|
|
252
|
-
model:
|
|
254
|
+
model: (this.config.createModel ?? defaultAnthropicProvider)(fastModel),
|
|
253
255
|
system: 'Summarize this conversation history into a concise context summary. Preserve key decisions, outcomes, and any information needed to continue the conversation. Keep under 500 words.',
|
|
254
256
|
messages: [{ role: 'user', content: textToSummarize.slice(0, 8000) }],
|
|
255
257
|
abortSignal: signal,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { generateText } from 'ai';
|
|
3
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
3
|
+
import { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';
|
|
4
|
+
import type { ModelFactory } from './types';
|
|
4
5
|
import type { AgentMessage } from '../agent/types';
|
|
5
6
|
import { getTextContent } from '../agent/types';
|
|
6
7
|
import type { Episode, EpisodeTrace } from './arc-types';
|
|
@@ -143,6 +144,12 @@ function extractArtifacts(messages: AgentMessage[]): EpisodeArtifact[] {
|
|
|
143
144
|
// ── EpisodeCompressor class ──
|
|
144
145
|
|
|
145
146
|
export class EpisodeCompressor {
|
|
147
|
+
private readonly createModel: ModelFactory;
|
|
148
|
+
|
|
149
|
+
constructor(createModel?: ModelFactory) {
|
|
150
|
+
this.createModel = createModel ?? defaultAnthropicProvider;
|
|
151
|
+
}
|
|
152
|
+
|
|
146
153
|
compress(input: CompressInput): CompressOutput {
|
|
147
154
|
const now = Date.now();
|
|
148
155
|
const id = randomUUID();
|
|
@@ -200,7 +207,7 @@ export class EpisodeCompressor {
|
|
|
200
207
|
|
|
201
208
|
const fastModel = resolveModel('fast', DEFAULT_MODEL_MAP, DEFAULT_MODEL_MAP.fast);
|
|
202
209
|
const llmResult = await generateText({
|
|
203
|
-
model:
|
|
210
|
+
model: this.createModel(fastModel),
|
|
204
211
|
system: 'Summarize this agent conversation into a concise episode summary. Focus on: what was attempted, what tools were used, what files were changed, and the outcome. Keep it under 300 words.',
|
|
205
212
|
messages: [{ role: 'user', content: conversationText.slice(0, 8000) }],
|
|
206
213
|
abortSignal: signal,
|
package/src/arc/types.ts
CHANGED
|
@@ -38,6 +38,12 @@ export interface EpisodeArtifact {
|
|
|
38
38
|
content: string; // verbatim extracted content
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// ── Model factory ──
|
|
42
|
+
|
|
43
|
+
/** Creates an ai-sdk LanguageModel from a model ID string. Defaults to anthropic(). */
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
export type ModelFactory = (modelId: string) => any;
|
|
46
|
+
|
|
41
47
|
// ── ArcLoop v2 Config ──
|
|
42
48
|
|
|
43
49
|
export interface ArcLoopConfig {
|
|
@@ -46,7 +52,9 @@ export interface ArcLoopConfig {
|
|
|
46
52
|
model?: string;
|
|
47
53
|
/** Model tier mapping. Override to use different models for fast/medium/strong. */
|
|
48
54
|
modelMap?: Record<import('./arc-types').ModelTier, string>;
|
|
49
|
-
/**
|
|
55
|
+
/** Model factory — creates an ai-sdk LanguageModel from a model ID. Defaults to anthropic(). */
|
|
56
|
+
createModel?: ModelFactory;
|
|
57
|
+
/** @deprecated Use createModel instead. Anthropic API key (set via ANTHROPIC_API_KEY env var). */
|
|
50
58
|
apiKey?: string;
|
|
51
59
|
/** Custom orchestrator system prompt */
|
|
52
60
|
systemPrompt?: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { generateText, streamText, stepCountIs, tool, type Tool } from 'ai';
|
|
2
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
2
|
+
import { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';
|
|
3
|
+
import type { ModelFactory } from '../arc/types';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
|
|
5
6
|
import type { AgentAction, AgentMessage, AgentLoop, AgentStreamEvent, ToolCallAction, ToolBatchAction } from '../agent/types';
|
|
@@ -161,6 +162,7 @@ function toModelMessages(messages: AgentMessage[]): ModelMessage[] {
|
|
|
161
162
|
|
|
162
163
|
export interface VercelAgentLoopConfig {
|
|
163
164
|
model?: string;
|
|
165
|
+
createModel?: ModelFactory;
|
|
164
166
|
systemPrompt?: string;
|
|
165
167
|
apiKey?: string;
|
|
166
168
|
/** Custom tool definitions. If provided, replaces built-in agentTools for LLM calls. */
|
|
@@ -169,8 +171,8 @@ export interface VercelAgentLoopConfig {
|
|
|
169
171
|
|
|
170
172
|
export class VercelAgentLoop implements AgentLoop {
|
|
171
173
|
private readonly model: string;
|
|
174
|
+
private readonly createModel: ModelFactory;
|
|
172
175
|
private readonly systemPrompt: string;
|
|
173
|
-
/** System prompt with Anthropic cache_control for prompt caching. */
|
|
174
176
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
177
|
private readonly cachedSystem: any;
|
|
176
178
|
private readonly tools: Record<string, AnyTool>;
|
|
@@ -178,6 +180,7 @@ export class VercelAgentLoop implements AgentLoop {
|
|
|
178
180
|
|
|
179
181
|
constructor(config: VercelAgentLoopConfig = {}) {
|
|
180
182
|
this.model = config.model ?? process.env.HARNESS_MODEL ?? 'claude-sonnet-4-5';
|
|
183
|
+
this.createModel = config.createModel ?? defaultAnthropicProvider;
|
|
181
184
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
185
|
this.tools = config.tools ?? builtinTools as any;
|
|
183
186
|
this.validToolNames = new Set(Object.keys(this.tools));
|
|
@@ -192,11 +195,9 @@ export class VercelAgentLoop implements AgentLoop {
|
|
|
192
195
|
'When the task is fully complete, respond with a brief text summary (no tool call).',
|
|
193
196
|
].join(' ');
|
|
194
197
|
|
|
195
|
-
// SystemModelMessage format with Anthropic cache_control
|
|
196
198
|
this.cachedSystem = [{
|
|
197
199
|
role: 'system' as const,
|
|
198
200
|
content: this.systemPrompt,
|
|
199
|
-
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } },
|
|
200
201
|
}];
|
|
201
202
|
|
|
202
203
|
if (config.apiKey) {
|
|
@@ -205,13 +206,10 @@ export class VercelAgentLoop implements AgentLoop {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
async nextAction(messages: AgentMessage[]): Promise<AgentAction> {
|
|
208
|
-
if (!process.env.ANTHROPIC_API_KEY) {
|
|
209
|
-
throw new Error('ANTHROPIC_API_KEY is required for default VercelAgentLoop');
|
|
210
|
-
}
|
|
211
209
|
|
|
212
210
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
213
211
|
const result = await (generateText as any)({
|
|
214
|
-
model:
|
|
212
|
+
model: this.createModel(this.model),
|
|
215
213
|
tools: this.tools,
|
|
216
214
|
toolChoice: 'auto',
|
|
217
215
|
system: this.cachedSystem,
|
|
@@ -254,13 +252,10 @@ export class VercelAgentLoop implements AgentLoop {
|
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
async *streamAction(messages: AgentMessage[]): AsyncGenerator<AgentStreamEvent> {
|
|
257
|
-
if (!process.env.ANTHROPIC_API_KEY) {
|
|
258
|
-
throw new Error('ANTHROPIC_API_KEY is required for default VercelAgentLoop');
|
|
259
|
-
}
|
|
260
255
|
|
|
261
256
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
262
257
|
const result = (streamText as any)({
|
|
263
|
-
model:
|
|
258
|
+
model: this.createModel(this.model),
|
|
264
259
|
tools: this.tools,
|
|
265
260
|
toolChoice: 'auto',
|
|
266
261
|
system: this.cachedSystem,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { generateObject } from 'ai';
|
|
2
|
-
import { anthropic } from '@ai-sdk/anthropic';
|
|
2
|
+
import { anthropic as defaultAnthropicProvider } from '@ai-sdk/anthropic';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
import type { SkillSummary } from './skill-types';
|
|
6
|
+
import type { ModelFactory } from '../arc/types';
|
|
6
7
|
|
|
7
8
|
const routeSchema = z.object({
|
|
8
9
|
skillName: z.string().nullable(),
|
|
@@ -12,6 +13,7 @@ const routeSchema = z.object({
|
|
|
12
13
|
|
|
13
14
|
export interface SkillRouterConfig {
|
|
14
15
|
model?: string;
|
|
16
|
+
createModel?: ModelFactory;
|
|
15
17
|
minConfidence?: number;
|
|
16
18
|
aliases?: Record<string, string[]>;
|
|
17
19
|
}
|
|
@@ -27,11 +29,13 @@ const DEFAULT_ALIASES: Record<string, string[]> = {
|
|
|
27
29
|
|
|
28
30
|
export class SkillRouter {
|
|
29
31
|
private readonly model: string;
|
|
32
|
+
private readonly createModel: ModelFactory;
|
|
30
33
|
private readonly minConfidence: number;
|
|
31
34
|
private readonly aliases: Record<string, string[]>;
|
|
32
35
|
|
|
33
36
|
constructor(config: SkillRouterConfig = {}) {
|
|
34
37
|
this.model = config.model ?? process.env.HARNESS_SKILL_ROUTER_MODEL ?? 'claude-3-5-haiku-latest';
|
|
38
|
+
this.createModel = config.createModel ?? defaultAnthropicProvider;
|
|
35
39
|
this.minConfidence = config.minConfidence ?? Number(process.env.HARNESS_SKILL_ROUTER_THRESHOLD ?? '0.55');
|
|
36
40
|
this.aliases = {
|
|
37
41
|
...DEFAULT_ALIASES,
|
|
@@ -53,9 +57,8 @@ export class SkillRouter {
|
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
if
|
|
57
|
-
|
|
58
|
-
}
|
|
60
|
+
// LLM-based routing — if no API key is configured, the provider will fail
|
|
61
|
+
// and the catch block below will return null (graceful fallback).
|
|
59
62
|
|
|
60
63
|
try {
|
|
61
64
|
const skillList = summaries
|
|
@@ -63,7 +66,7 @@ export class SkillRouter {
|
|
|
63
66
|
.join('\n');
|
|
64
67
|
|
|
65
68
|
const { object } = await generateObject({
|
|
66
|
-
model:
|
|
69
|
+
model: this.createModel(this.model),
|
|
67
70
|
schema: routeSchema,
|
|
68
71
|
system: [
|
|
69
72
|
'You are a skill router.',
|