@harness-engineering/orchestrator 0.2.6 → 0.2.7
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/dist/index.d.mts +65 -1
- package/dist/index.d.ts +65 -1
- package/dist/index.js +368 -10
- package/dist/index.mjs +365 -1
- package/package.json +6 -3
package/dist/index.d.mts
CHANGED
|
@@ -344,6 +344,70 @@ declare class ClaudeBackend implements AgentBackend {
|
|
|
344
344
|
healthCheck(): Promise<Result<void, AgentError>>;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
interface OpenAIBackendConfig {
|
|
348
|
+
/** OpenAI model to use. Defaults to 'gpt-4o'. */
|
|
349
|
+
model?: string;
|
|
350
|
+
/** API key. Defaults to process.env.OPENAI_API_KEY. */
|
|
351
|
+
apiKey?: string;
|
|
352
|
+
}
|
|
353
|
+
interface OpenAISession extends AgentSession {
|
|
354
|
+
systemPrompt?: string;
|
|
355
|
+
}
|
|
356
|
+
declare class OpenAIBackend implements AgentBackend {
|
|
357
|
+
readonly name = "openai";
|
|
358
|
+
private config;
|
|
359
|
+
private client;
|
|
360
|
+
private cacheAdapter;
|
|
361
|
+
constructor(config?: OpenAIBackendConfig);
|
|
362
|
+
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
363
|
+
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
364
|
+
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
365
|
+
healthCheck(): Promise<Result<void, AgentError>>;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
interface GeminiBackendConfig {
|
|
369
|
+
/** Gemini model to use. Defaults to 'gemini-2.0-flash'. */
|
|
370
|
+
model?: string;
|
|
371
|
+
/** API key. Defaults to process.env.GEMINI_API_KEY or process.env.GOOGLE_API_KEY. */
|
|
372
|
+
apiKey?: string;
|
|
373
|
+
}
|
|
374
|
+
interface GeminiSession extends AgentSession {
|
|
375
|
+
systemPrompt?: string;
|
|
376
|
+
}
|
|
377
|
+
declare class GeminiBackend implements AgentBackend {
|
|
378
|
+
readonly name = "gemini";
|
|
379
|
+
private config;
|
|
380
|
+
private cacheAdapter;
|
|
381
|
+
constructor(config?: GeminiBackendConfig);
|
|
382
|
+
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
383
|
+
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
384
|
+
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
385
|
+
healthCheck(): Promise<Result<void, AgentError>>;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
interface AnthropicBackendConfig {
|
|
389
|
+
/** Anthropic model to use. Defaults to 'claude-sonnet-4-20250514'. */
|
|
390
|
+
model?: string;
|
|
391
|
+
/** API key. Defaults to process.env.ANTHROPIC_API_KEY. */
|
|
392
|
+
apiKey?: string;
|
|
393
|
+
/** Maximum output tokens. Defaults to 4096. */
|
|
394
|
+
maxTokens?: number;
|
|
395
|
+
}
|
|
396
|
+
interface AnthropicSession extends AgentSession {
|
|
397
|
+
systemPrompt?: string;
|
|
398
|
+
}
|
|
399
|
+
declare class AnthropicBackend implements AgentBackend {
|
|
400
|
+
readonly name = "anthropic";
|
|
401
|
+
private config;
|
|
402
|
+
private client;
|
|
403
|
+
private cacheAdapter;
|
|
404
|
+
constructor(config?: AnthropicBackendConfig);
|
|
405
|
+
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
406
|
+
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
407
|
+
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
408
|
+
healthCheck(): Promise<Result<void, AgentError>>;
|
|
409
|
+
}
|
|
410
|
+
|
|
347
411
|
declare class PromptRenderer {
|
|
348
412
|
private engine;
|
|
349
413
|
constructor();
|
|
@@ -440,4 +504,4 @@ declare function launchTUI(orchestrator: Orchestrator): {
|
|
|
440
504
|
waitUntilExit: () => Promise<void>;
|
|
441
505
|
};
|
|
442
506
|
|
|
443
|
-
export { type AgentUpdateEvent, type ApplyEventResult, ClaudeBackend, type CleanWorkspaceEffect, type DispatchEffect, type EmitLogEffect, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, MockBackend, Orchestrator, type OrchestratorEvent, type OrchestratorState, PromptRenderer, type RateLimitSnapshot, type ReleaseClaimEffect, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, type RunAttemptPhase, type RunningEntry, type ScheduleRetryEffect, type SideEffect, type StallDetectedEvent, type StopEffect, type TickEvent, type TokenTotals, type UpdateTokensEffect, type WorkerExitEvent, WorkflowLoader, WorkspaceHooks, WorkspaceManager, applyEvent, calculateRetryDelay, canDispatch, createEmptyState, getAvailableSlots, getDefaultConfig, getPerStateCount, isEligible, launchTUI, reconcile, selectCandidates, sortCandidates, validateWorkflowConfig };
|
|
507
|
+
export { type AgentUpdateEvent, AnthropicBackend, type AnthropicBackendConfig, type AnthropicSession, type ApplyEventResult, ClaudeBackend, type CleanWorkspaceEffect, type DispatchEffect, type EmitLogEffect, GeminiBackend, type GeminiBackendConfig, type GeminiSession, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, MockBackend, OpenAIBackend, type OpenAIBackendConfig, type OpenAISession, Orchestrator, type OrchestratorEvent, type OrchestratorState, PromptRenderer, type RateLimitSnapshot, type ReleaseClaimEffect, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, type RunAttemptPhase, type RunningEntry, type ScheduleRetryEffect, type SideEffect, type StallDetectedEvent, type StopEffect, type TickEvent, type TokenTotals, type UpdateTokensEffect, type WorkerExitEvent, WorkflowLoader, WorkspaceHooks, WorkspaceManager, applyEvent, calculateRetryDelay, canDispatch, createEmptyState, getAvailableSlots, getDefaultConfig, getPerStateCount, isEligible, launchTUI, reconcile, selectCandidates, sortCandidates, validateWorkflowConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -344,6 +344,70 @@ declare class ClaudeBackend implements AgentBackend {
|
|
|
344
344
|
healthCheck(): Promise<Result<void, AgentError>>;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
interface OpenAIBackendConfig {
|
|
348
|
+
/** OpenAI model to use. Defaults to 'gpt-4o'. */
|
|
349
|
+
model?: string;
|
|
350
|
+
/** API key. Defaults to process.env.OPENAI_API_KEY. */
|
|
351
|
+
apiKey?: string;
|
|
352
|
+
}
|
|
353
|
+
interface OpenAISession extends AgentSession {
|
|
354
|
+
systemPrompt?: string;
|
|
355
|
+
}
|
|
356
|
+
declare class OpenAIBackend implements AgentBackend {
|
|
357
|
+
readonly name = "openai";
|
|
358
|
+
private config;
|
|
359
|
+
private client;
|
|
360
|
+
private cacheAdapter;
|
|
361
|
+
constructor(config?: OpenAIBackendConfig);
|
|
362
|
+
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
363
|
+
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
364
|
+
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
365
|
+
healthCheck(): Promise<Result<void, AgentError>>;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
interface GeminiBackendConfig {
|
|
369
|
+
/** Gemini model to use. Defaults to 'gemini-2.0-flash'. */
|
|
370
|
+
model?: string;
|
|
371
|
+
/** API key. Defaults to process.env.GEMINI_API_KEY or process.env.GOOGLE_API_KEY. */
|
|
372
|
+
apiKey?: string;
|
|
373
|
+
}
|
|
374
|
+
interface GeminiSession extends AgentSession {
|
|
375
|
+
systemPrompt?: string;
|
|
376
|
+
}
|
|
377
|
+
declare class GeminiBackend implements AgentBackend {
|
|
378
|
+
readonly name = "gemini";
|
|
379
|
+
private config;
|
|
380
|
+
private cacheAdapter;
|
|
381
|
+
constructor(config?: GeminiBackendConfig);
|
|
382
|
+
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
383
|
+
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
384
|
+
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
385
|
+
healthCheck(): Promise<Result<void, AgentError>>;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
interface AnthropicBackendConfig {
|
|
389
|
+
/** Anthropic model to use. Defaults to 'claude-sonnet-4-20250514'. */
|
|
390
|
+
model?: string;
|
|
391
|
+
/** API key. Defaults to process.env.ANTHROPIC_API_KEY. */
|
|
392
|
+
apiKey?: string;
|
|
393
|
+
/** Maximum output tokens. Defaults to 4096. */
|
|
394
|
+
maxTokens?: number;
|
|
395
|
+
}
|
|
396
|
+
interface AnthropicSession extends AgentSession {
|
|
397
|
+
systemPrompt?: string;
|
|
398
|
+
}
|
|
399
|
+
declare class AnthropicBackend implements AgentBackend {
|
|
400
|
+
readonly name = "anthropic";
|
|
401
|
+
private config;
|
|
402
|
+
private client;
|
|
403
|
+
private cacheAdapter;
|
|
404
|
+
constructor(config?: AnthropicBackendConfig);
|
|
405
|
+
startSession(params: SessionStartParams): Promise<Result<AgentSession, AgentError>>;
|
|
406
|
+
runTurn(session: AgentSession, params: TurnParams): AsyncGenerator<AgentEvent, TurnResult, void>;
|
|
407
|
+
stopSession(_session: AgentSession): Promise<Result<void, AgentError>>;
|
|
408
|
+
healthCheck(): Promise<Result<void, AgentError>>;
|
|
409
|
+
}
|
|
410
|
+
|
|
347
411
|
declare class PromptRenderer {
|
|
348
412
|
private engine;
|
|
349
413
|
constructor();
|
|
@@ -440,4 +504,4 @@ declare function launchTUI(orchestrator: Orchestrator): {
|
|
|
440
504
|
waitUntilExit: () => Promise<void>;
|
|
441
505
|
};
|
|
442
506
|
|
|
443
|
-
export { type AgentUpdateEvent, type ApplyEventResult, ClaudeBackend, type CleanWorkspaceEffect, type DispatchEffect, type EmitLogEffect, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, MockBackend, Orchestrator, type OrchestratorEvent, type OrchestratorState, PromptRenderer, type RateLimitSnapshot, type ReleaseClaimEffect, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, type RunAttemptPhase, type RunningEntry, type ScheduleRetryEffect, type SideEffect, type StallDetectedEvent, type StopEffect, type TickEvent, type TokenTotals, type UpdateTokensEffect, type WorkerExitEvent, WorkflowLoader, WorkspaceHooks, WorkspaceManager, applyEvent, calculateRetryDelay, canDispatch, createEmptyState, getAvailableSlots, getDefaultConfig, getPerStateCount, isEligible, launchTUI, reconcile, selectCandidates, sortCandidates, validateWorkflowConfig };
|
|
507
|
+
export { type AgentUpdateEvent, AnthropicBackend, type AnthropicBackendConfig, type AnthropicSession, type ApplyEventResult, ClaudeBackend, type CleanWorkspaceEffect, type DispatchEffect, type EmitLogEffect, GeminiBackend, type GeminiBackendConfig, type GeminiSession, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, MockBackend, OpenAIBackend, type OpenAIBackendConfig, type OpenAISession, Orchestrator, type OrchestratorEvent, type OrchestratorState, PromptRenderer, type RateLimitSnapshot, type ReleaseClaimEffect, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, type RunAttemptPhase, type RunningEntry, type ScheduleRetryEffect, type SideEffect, type StallDetectedEvent, type StopEffect, type TickEvent, type TokenTotals, type UpdateTokensEffect, type WorkerExitEvent, WorkflowLoader, WorkspaceHooks, WorkspaceManager, applyEvent, calculateRetryDelay, canDispatch, createEmptyState, getAvailableSlots, getDefaultConfig, getPerStateCount, isEligible, launchTUI, reconcile, selectCandidates, sortCandidates, validateWorkflowConfig };
|
package/dist/index.js
CHANGED
|
@@ -30,9 +30,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AnthropicBackend: () => AnthropicBackend,
|
|
33
34
|
ClaudeBackend: () => ClaudeBackend,
|
|
35
|
+
GeminiBackend: () => GeminiBackend,
|
|
34
36
|
LinearGraphQLStub: () => LinearGraphQLStub,
|
|
35
37
|
MockBackend: () => MockBackend,
|
|
38
|
+
OpenAIBackend: () => OpenAIBackend,
|
|
36
39
|
Orchestrator: () => Orchestrator,
|
|
37
40
|
PromptRenderer: () => PromptRenderer,
|
|
38
41
|
RoadmapTrackerAdapter: () => RoadmapTrackerAdapter,
|
|
@@ -844,6 +847,335 @@ var ClaudeBackend = class {
|
|
|
844
847
|
}
|
|
845
848
|
};
|
|
846
849
|
|
|
850
|
+
// src/agent/backends/openai.ts
|
|
851
|
+
var import_openai = __toESM(require("openai"));
|
|
852
|
+
var import_types9 = require("@harness-engineering/types");
|
|
853
|
+
var import_core2 = require("@harness-engineering/core");
|
|
854
|
+
var OpenAIBackend = class {
|
|
855
|
+
name = "openai";
|
|
856
|
+
config;
|
|
857
|
+
client;
|
|
858
|
+
cacheAdapter;
|
|
859
|
+
constructor(config = {}) {
|
|
860
|
+
this.config = {
|
|
861
|
+
model: config.model ?? "gpt-4o",
|
|
862
|
+
apiKey: config.apiKey ?? process.env.OPENAI_API_KEY ?? ""
|
|
863
|
+
};
|
|
864
|
+
this.client = new import_openai.default({ apiKey: this.config.apiKey });
|
|
865
|
+
this.cacheAdapter = new import_core2.OpenAICacheAdapter();
|
|
866
|
+
}
|
|
867
|
+
async startSession(params) {
|
|
868
|
+
if (!this.config.apiKey) {
|
|
869
|
+
return (0, import_types9.Err)({
|
|
870
|
+
category: "agent_not_found",
|
|
871
|
+
message: "OPENAI_API_KEY is not set"
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
const session = {
|
|
875
|
+
sessionId: `openai-session-${Date.now()}`,
|
|
876
|
+
workspacePath: params.workspacePath,
|
|
877
|
+
backendName: this.name,
|
|
878
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
879
|
+
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
880
|
+
};
|
|
881
|
+
return (0, import_types9.Ok)(session);
|
|
882
|
+
}
|
|
883
|
+
async *runTurn(session, params) {
|
|
884
|
+
const openAISession = session;
|
|
885
|
+
const messages = [];
|
|
886
|
+
if (openAISession.systemPrompt) {
|
|
887
|
+
messages.push({ role: "system", content: openAISession.systemPrompt });
|
|
888
|
+
}
|
|
889
|
+
messages.push({ role: "user", content: params.prompt });
|
|
890
|
+
let inputTokens = 0;
|
|
891
|
+
let outputTokens = 0;
|
|
892
|
+
let totalTokens = 0;
|
|
893
|
+
let cacheCreationTokens = 0;
|
|
894
|
+
let cacheReadTokens = 0;
|
|
895
|
+
try {
|
|
896
|
+
const stream = await this.client.chat.completions.create({
|
|
897
|
+
model: this.config.model,
|
|
898
|
+
messages,
|
|
899
|
+
stream: true,
|
|
900
|
+
stream_options: { include_usage: true }
|
|
901
|
+
});
|
|
902
|
+
for await (const chunk of stream) {
|
|
903
|
+
const delta = chunk.choices[0]?.delta;
|
|
904
|
+
if (delta?.content) {
|
|
905
|
+
const event = {
|
|
906
|
+
type: "text",
|
|
907
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
908
|
+
content: delta.content,
|
|
909
|
+
sessionId: session.sessionId
|
|
910
|
+
};
|
|
911
|
+
yield event;
|
|
912
|
+
}
|
|
913
|
+
if (chunk.usage) {
|
|
914
|
+
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
915
|
+
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
916
|
+
totalTokens = chunk.usage.total_tokens ?? 0;
|
|
917
|
+
const cacheUsage = this.cacheAdapter.parseCacheUsage(chunk);
|
|
918
|
+
cacheCreationTokens = cacheUsage.cacheCreationTokens;
|
|
919
|
+
cacheReadTokens = cacheUsage.cacheReadTokens;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
} catch (err) {
|
|
923
|
+
const errorMessage = err instanceof Error ? err.message : "OpenAI request failed";
|
|
924
|
+
yield {
|
|
925
|
+
type: "error",
|
|
926
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
927
|
+
content: errorMessage,
|
|
928
|
+
sessionId: session.sessionId
|
|
929
|
+
};
|
|
930
|
+
return {
|
|
931
|
+
success: false,
|
|
932
|
+
sessionId: session.sessionId,
|
|
933
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
934
|
+
error: errorMessage
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
success: true,
|
|
939
|
+
sessionId: session.sessionId,
|
|
940
|
+
usage: {
|
|
941
|
+
inputTokens,
|
|
942
|
+
outputTokens,
|
|
943
|
+
totalTokens,
|
|
944
|
+
cacheCreationTokens,
|
|
945
|
+
cacheReadTokens
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
async stopSession(_session) {
|
|
950
|
+
return (0, import_types9.Ok)(void 0);
|
|
951
|
+
}
|
|
952
|
+
async healthCheck() {
|
|
953
|
+
try {
|
|
954
|
+
await this.client.models.list();
|
|
955
|
+
return (0, import_types9.Ok)(void 0);
|
|
956
|
+
} catch (err) {
|
|
957
|
+
return (0, import_types9.Err)({
|
|
958
|
+
category: "response_error",
|
|
959
|
+
message: err instanceof Error ? err.message : "OpenAI health check failed"
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/agent/backends/gemini.ts
|
|
966
|
+
var import_generative_ai = require("@google/generative-ai");
|
|
967
|
+
var import_types10 = require("@harness-engineering/types");
|
|
968
|
+
var import_core3 = require("@harness-engineering/core");
|
|
969
|
+
var GeminiBackend = class {
|
|
970
|
+
name = "gemini";
|
|
971
|
+
config;
|
|
972
|
+
cacheAdapter;
|
|
973
|
+
constructor(config = {}) {
|
|
974
|
+
this.config = {
|
|
975
|
+
model: config.model ?? "gemini-2.0-flash",
|
|
976
|
+
apiKey: config.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? ""
|
|
977
|
+
};
|
|
978
|
+
this.cacheAdapter = new import_core3.GeminiCacheAdapter();
|
|
979
|
+
}
|
|
980
|
+
async startSession(params) {
|
|
981
|
+
if (!this.config.apiKey) {
|
|
982
|
+
return (0, import_types10.Err)({
|
|
983
|
+
category: "agent_not_found",
|
|
984
|
+
message: "GEMINI_API_KEY is not set"
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
const session = {
|
|
988
|
+
sessionId: `gemini-session-${Date.now()}`,
|
|
989
|
+
workspacePath: params.workspacePath,
|
|
990
|
+
backendName: this.name,
|
|
991
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
992
|
+
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
993
|
+
};
|
|
994
|
+
return (0, import_types10.Ok)(session);
|
|
995
|
+
}
|
|
996
|
+
async *runTurn(session, params) {
|
|
997
|
+
const geminiSession = session;
|
|
998
|
+
let inputTokens = 0;
|
|
999
|
+
let outputTokens = 0;
|
|
1000
|
+
let totalTokens = 0;
|
|
1001
|
+
let cacheCreationTokens = 0;
|
|
1002
|
+
let cacheReadTokens = 0;
|
|
1003
|
+
try {
|
|
1004
|
+
const genAI = new import_generative_ai.GoogleGenerativeAI(this.config.apiKey);
|
|
1005
|
+
const model = genAI.getGenerativeModel({
|
|
1006
|
+
model: this.config.model,
|
|
1007
|
+
...geminiSession.systemPrompt !== void 0 && {
|
|
1008
|
+
systemInstruction: geminiSession.systemPrompt
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
const result = await model.generateContentStream(params.prompt);
|
|
1012
|
+
for await (const chunk of result.stream) {
|
|
1013
|
+
const text = chunk.text();
|
|
1014
|
+
if (text) {
|
|
1015
|
+
const event = {
|
|
1016
|
+
type: "text",
|
|
1017
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1018
|
+
content: text,
|
|
1019
|
+
sessionId: session.sessionId
|
|
1020
|
+
};
|
|
1021
|
+
yield event;
|
|
1022
|
+
}
|
|
1023
|
+
if (chunk.usageMetadata) {
|
|
1024
|
+
inputTokens = chunk.usageMetadata.promptTokenCount ?? 0;
|
|
1025
|
+
outputTokens = chunk.usageMetadata.candidatesTokenCount ?? 0;
|
|
1026
|
+
totalTokens = chunk.usageMetadata.totalTokenCount ?? 0;
|
|
1027
|
+
const cacheUsage = this.cacheAdapter.parseCacheUsage(chunk);
|
|
1028
|
+
cacheCreationTokens = cacheUsage.cacheCreationTokens;
|
|
1029
|
+
cacheReadTokens = cacheUsage.cacheReadTokens;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
const errorMessage = err instanceof Error ? err.message : "Gemini request failed";
|
|
1034
|
+
yield {
|
|
1035
|
+
type: "error",
|
|
1036
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1037
|
+
content: errorMessage,
|
|
1038
|
+
sessionId: session.sessionId
|
|
1039
|
+
};
|
|
1040
|
+
return {
|
|
1041
|
+
success: false,
|
|
1042
|
+
sessionId: session.sessionId,
|
|
1043
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
1044
|
+
error: errorMessage
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
return {
|
|
1048
|
+
success: true,
|
|
1049
|
+
sessionId: session.sessionId,
|
|
1050
|
+
usage: {
|
|
1051
|
+
inputTokens,
|
|
1052
|
+
outputTokens,
|
|
1053
|
+
totalTokens,
|
|
1054
|
+
cacheCreationTokens,
|
|
1055
|
+
cacheReadTokens
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
async stopSession(_session) {
|
|
1060
|
+
return (0, import_types10.Ok)(void 0);
|
|
1061
|
+
}
|
|
1062
|
+
async healthCheck() {
|
|
1063
|
+
try {
|
|
1064
|
+
const genAI = new import_generative_ai.GoogleGenerativeAI(this.config.apiKey);
|
|
1065
|
+
genAI.getGenerativeModel({ model: this.config.model });
|
|
1066
|
+
return (0, import_types10.Ok)(void 0);
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
return (0, import_types10.Err)({
|
|
1069
|
+
category: "response_error",
|
|
1070
|
+
message: err instanceof Error ? err.message : "Gemini health check failed"
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// src/agent/backends/anthropic.ts
|
|
1077
|
+
var import_sdk = __toESM(require("@anthropic-ai/sdk"));
|
|
1078
|
+
var import_types11 = require("@harness-engineering/types");
|
|
1079
|
+
var import_core4 = require("@harness-engineering/core");
|
|
1080
|
+
var AnthropicBackend = class {
|
|
1081
|
+
name = "anthropic";
|
|
1082
|
+
config;
|
|
1083
|
+
client;
|
|
1084
|
+
cacheAdapter;
|
|
1085
|
+
constructor(config = {}) {
|
|
1086
|
+
this.config = {
|
|
1087
|
+
model: config.model ?? "claude-sonnet-4-20250514",
|
|
1088
|
+
apiKey: config.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "",
|
|
1089
|
+
maxTokens: config.maxTokens ?? 4096
|
|
1090
|
+
};
|
|
1091
|
+
this.client = new import_sdk.default({ apiKey: this.config.apiKey });
|
|
1092
|
+
this.cacheAdapter = new import_core4.AnthropicCacheAdapter();
|
|
1093
|
+
}
|
|
1094
|
+
async startSession(params) {
|
|
1095
|
+
if (!this.config.apiKey) {
|
|
1096
|
+
return (0, import_types11.Err)({
|
|
1097
|
+
category: "agent_not_found",
|
|
1098
|
+
message: "ANTHROPIC_API_KEY is not set"
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
const session = {
|
|
1102
|
+
sessionId: `anthropic-session-${Date.now()}`,
|
|
1103
|
+
workspacePath: params.workspacePath,
|
|
1104
|
+
backendName: this.name,
|
|
1105
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1106
|
+
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
1107
|
+
};
|
|
1108
|
+
return (0, import_types11.Ok)(session);
|
|
1109
|
+
}
|
|
1110
|
+
async *runTurn(session, params) {
|
|
1111
|
+
const anthropicSession = session;
|
|
1112
|
+
const systemBlocks = anthropicSession.systemPrompt ? [
|
|
1113
|
+
this.cacheAdapter.wrapSystemBlock(
|
|
1114
|
+
anthropicSession.systemPrompt,
|
|
1115
|
+
"session"
|
|
1116
|
+
)
|
|
1117
|
+
] : void 0;
|
|
1118
|
+
try {
|
|
1119
|
+
const stream = this.client.messages.stream({
|
|
1120
|
+
model: this.config.model,
|
|
1121
|
+
max_tokens: this.config.maxTokens,
|
|
1122
|
+
...systemBlocks && { system: systemBlocks },
|
|
1123
|
+
messages: [{ role: "user", content: params.prompt }]
|
|
1124
|
+
});
|
|
1125
|
+
for await (const event of stream) {
|
|
1126
|
+
if (event.type === "content_block_delta" && "text" in event.delta) {
|
|
1127
|
+
yield {
|
|
1128
|
+
type: "text",
|
|
1129
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1130
|
+
content: event.delta.text,
|
|
1131
|
+
sessionId: session.sessionId
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
const finalMessage = await stream.finalMessage();
|
|
1136
|
+
const { input_tokens, output_tokens } = finalMessage.usage;
|
|
1137
|
+
const cacheUsage = this.cacheAdapter.parseCacheUsage(finalMessage);
|
|
1138
|
+
return {
|
|
1139
|
+
success: true,
|
|
1140
|
+
sessionId: session.sessionId,
|
|
1141
|
+
usage: {
|
|
1142
|
+
inputTokens: input_tokens,
|
|
1143
|
+
outputTokens: output_tokens,
|
|
1144
|
+
totalTokens: input_tokens + output_tokens,
|
|
1145
|
+
cacheCreationTokens: cacheUsage.cacheCreationTokens,
|
|
1146
|
+
cacheReadTokens: cacheUsage.cacheReadTokens
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
} catch (err) {
|
|
1150
|
+
const errorMessage = err instanceof Error ? err.message : "Anthropic request failed";
|
|
1151
|
+
yield {
|
|
1152
|
+
type: "error",
|
|
1153
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1154
|
+
content: errorMessage,
|
|
1155
|
+
sessionId: session.sessionId
|
|
1156
|
+
};
|
|
1157
|
+
return {
|
|
1158
|
+
success: false,
|
|
1159
|
+
sessionId: session.sessionId,
|
|
1160
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
1161
|
+
error: errorMessage
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
async stopSession(_session) {
|
|
1166
|
+
return (0, import_types11.Ok)(void 0);
|
|
1167
|
+
}
|
|
1168
|
+
async healthCheck() {
|
|
1169
|
+
if (!this.config.apiKey) {
|
|
1170
|
+
return (0, import_types11.Err)({
|
|
1171
|
+
category: "response_error",
|
|
1172
|
+
message: "ANTHROPIC_API_KEY is not set"
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
return (0, import_types11.Ok)(void 0);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
|
|
847
1179
|
// src/prompt/renderer.ts
|
|
848
1180
|
var import_liquidjs = require("liquidjs");
|
|
849
1181
|
var PromptRenderer = class {
|
|
@@ -868,7 +1200,7 @@ var PromptRenderer = class {
|
|
|
868
1200
|
|
|
869
1201
|
// src/orchestrator.ts
|
|
870
1202
|
var import_node_events = require("events");
|
|
871
|
-
var
|
|
1203
|
+
var import_core6 = require("@harness-engineering/core");
|
|
872
1204
|
|
|
873
1205
|
// src/agent/runner.ts
|
|
874
1206
|
var AgentRunner = class {
|
|
@@ -895,7 +1227,13 @@ var AgentRunner = class {
|
|
|
895
1227
|
let lastResult = {
|
|
896
1228
|
success: false,
|
|
897
1229
|
sessionId: session.sessionId,
|
|
898
|
-
usage: {
|
|
1230
|
+
usage: {
|
|
1231
|
+
inputTokens: 0,
|
|
1232
|
+
outputTokens: 0,
|
|
1233
|
+
totalTokens: 0,
|
|
1234
|
+
cacheCreationTokens: 0,
|
|
1235
|
+
cacheReadTokens: 0
|
|
1236
|
+
}
|
|
899
1237
|
};
|
|
900
1238
|
try {
|
|
901
1239
|
while (currentTurn < this.options.maxTurns) {
|
|
@@ -994,7 +1332,7 @@ var StructuredLogger = class {
|
|
|
994
1332
|
// src/workspace/config-scanner.ts
|
|
995
1333
|
var import_node_fs = require("fs");
|
|
996
1334
|
var import_node_path = require("path");
|
|
997
|
-
var
|
|
1335
|
+
var import_core5 = require("@harness-engineering/core");
|
|
998
1336
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
999
1337
|
function scanSingleFile(filePath, targetDir, scanner) {
|
|
1000
1338
|
if (!(0, import_node_fs.existsSync)(filePath)) return null;
|
|
@@ -1004,24 +1342,24 @@ function scanSingleFile(filePath, targetDir, scanner) {
|
|
|
1004
1342
|
} catch {
|
|
1005
1343
|
return null;
|
|
1006
1344
|
}
|
|
1007
|
-
const injectionFindings = (0,
|
|
1008
|
-
const findings = (0,
|
|
1345
|
+
const injectionFindings = (0, import_core5.scanForInjection)(content);
|
|
1346
|
+
const findings = (0, import_core5.mapInjectionFindings)(injectionFindings);
|
|
1009
1347
|
const secFindings = scanner.scanContent(content, filePath);
|
|
1010
|
-
findings.push(...(0,
|
|
1348
|
+
findings.push(...(0, import_core5.mapSecurityFindings)(secFindings, findings));
|
|
1011
1349
|
return {
|
|
1012
1350
|
file: (0, import_node_path.relative)(targetDir, filePath).replaceAll("\\", "/"),
|
|
1013
1351
|
findings,
|
|
1014
|
-
overallSeverity: (0,
|
|
1352
|
+
overallSeverity: (0, import_core5.computeOverallSeverity)(findings)
|
|
1015
1353
|
};
|
|
1016
1354
|
}
|
|
1017
1355
|
async function scanWorkspaceConfig(workspacePath) {
|
|
1018
|
-
const scanner = new
|
|
1356
|
+
const scanner = new import_core5.SecurityScanner((0, import_core5.parseSecurityConfig)({}));
|
|
1019
1357
|
const results = [];
|
|
1020
1358
|
for (const configFile of CONFIG_FILES) {
|
|
1021
1359
|
const result = scanSingleFile((0, import_node_path.join)(workspacePath, configFile), workspacePath, scanner);
|
|
1022
1360
|
if (result) results.push(result);
|
|
1023
1361
|
}
|
|
1024
|
-
return { exitCode: (0,
|
|
1362
|
+
return { exitCode: (0, import_core5.computeScanExitCode)(results), results };
|
|
1025
1363
|
}
|
|
1026
1364
|
|
|
1027
1365
|
// src/orchestrator.ts
|
|
@@ -1072,6 +1410,21 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
1072
1410
|
return new MockBackend();
|
|
1073
1411
|
} else if (this.config.agent.backend === "claude") {
|
|
1074
1412
|
return new ClaudeBackend(this.config.agent.command);
|
|
1413
|
+
} else if (this.config.agent.backend === "openai") {
|
|
1414
|
+
return new OpenAIBackend({
|
|
1415
|
+
...this.config.agent.model !== void 0 && { model: this.config.agent.model },
|
|
1416
|
+
...this.config.agent.apiKey !== void 0 && { apiKey: this.config.agent.apiKey }
|
|
1417
|
+
});
|
|
1418
|
+
} else if (this.config.agent.backend === "gemini") {
|
|
1419
|
+
return new GeminiBackend({
|
|
1420
|
+
...this.config.agent.model !== void 0 && { model: this.config.agent.model },
|
|
1421
|
+
...this.config.agent.apiKey !== void 0 && { apiKey: this.config.agent.apiKey }
|
|
1422
|
+
});
|
|
1423
|
+
} else if (this.config.agent.backend === "anthropic") {
|
|
1424
|
+
return new AnthropicBackend({
|
|
1425
|
+
...this.config.agent.model !== void 0 && { model: this.config.agent.model },
|
|
1426
|
+
...this.config.agent.apiKey !== void 0 && { apiKey: this.config.agent.apiKey }
|
|
1427
|
+
});
|
|
1075
1428
|
}
|
|
1076
1429
|
throw new Error(`Unsupported agent backend: ${this.config.agent.backend}`);
|
|
1077
1430
|
}
|
|
@@ -1173,7 +1526,7 @@ var Orchestrator = class extends import_node_events.EventEmitter {
|
|
|
1173
1526
|
...f.line !== void 0 ? { line: f.line } : {}
|
|
1174
1527
|
}))
|
|
1175
1528
|
);
|
|
1176
|
-
(0,
|
|
1529
|
+
(0, import_core6.writeTaint)(
|
|
1177
1530
|
workspacePath,
|
|
1178
1531
|
issue.id,
|
|
1179
1532
|
"Medium-severity injection patterns found in workspace config files",
|
|
@@ -1370,12 +1723,14 @@ var AgentsTable = ({ agents }) => {
|
|
|
1370
1723
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, underline: true, children: "Active Agents" }),
|
|
1371
1724
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Box, { flexDirection: "row", borderStyle: "single", borderColor: "gray", children: [
|
|
1372
1725
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 20, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, children: "Identifier" }) }),
|
|
1726
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 12, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, children: "Backend" }) }),
|
|
1373
1727
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 20, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, children: "Phase" }) }),
|
|
1374
1728
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 10, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, children: "Tokens" }) }),
|
|
1375
1729
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { bold: true, children: "Message" }) })
|
|
1376
1730
|
] }),
|
|
1377
1731
|
agents.map((agent) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Box, { flexDirection: "row", children: [
|
|
1378
1732
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 20, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: agent.identifier }) }),
|
|
1733
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 12, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "blue", children: agent.session?.backendName || "-" }) }),
|
|
1379
1734
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 20, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", children: agent.phase }) }),
|
|
1380
1735
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { width: 10, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: agent.session?.totalTokens || 0 }) }),
|
|
1381
1736
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Box, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { wrap: "truncate-end", children: agent.session?.lastMessage || "-" }) })
|
|
@@ -1427,9 +1782,12 @@ function launchTUI(orchestrator) {
|
|
|
1427
1782
|
}
|
|
1428
1783
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1429
1784
|
0 && (module.exports = {
|
|
1785
|
+
AnthropicBackend,
|
|
1430
1786
|
ClaudeBackend,
|
|
1787
|
+
GeminiBackend,
|
|
1431
1788
|
LinearGraphQLStub,
|
|
1432
1789
|
MockBackend,
|
|
1790
|
+
OpenAIBackend,
|
|
1433
1791
|
Orchestrator,
|
|
1434
1792
|
PromptRenderer,
|
|
1435
1793
|
RoadmapTrackerAdapter,
|
package/dist/index.mjs
CHANGED
|
@@ -795,6 +795,344 @@ var ClaudeBackend = class {
|
|
|
795
795
|
}
|
|
796
796
|
};
|
|
797
797
|
|
|
798
|
+
// src/agent/backends/openai.ts
|
|
799
|
+
import OpenAI from "openai";
|
|
800
|
+
import {
|
|
801
|
+
Ok as Ok9,
|
|
802
|
+
Err as Err7
|
|
803
|
+
} from "@harness-engineering/types";
|
|
804
|
+
import { OpenAICacheAdapter } from "@harness-engineering/core";
|
|
805
|
+
var OpenAIBackend = class {
|
|
806
|
+
name = "openai";
|
|
807
|
+
config;
|
|
808
|
+
client;
|
|
809
|
+
cacheAdapter;
|
|
810
|
+
constructor(config = {}) {
|
|
811
|
+
this.config = {
|
|
812
|
+
model: config.model ?? "gpt-4o",
|
|
813
|
+
apiKey: config.apiKey ?? process.env.OPENAI_API_KEY ?? ""
|
|
814
|
+
};
|
|
815
|
+
this.client = new OpenAI({ apiKey: this.config.apiKey });
|
|
816
|
+
this.cacheAdapter = new OpenAICacheAdapter();
|
|
817
|
+
}
|
|
818
|
+
async startSession(params) {
|
|
819
|
+
if (!this.config.apiKey) {
|
|
820
|
+
return Err7({
|
|
821
|
+
category: "agent_not_found",
|
|
822
|
+
message: "OPENAI_API_KEY is not set"
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
const session = {
|
|
826
|
+
sessionId: `openai-session-${Date.now()}`,
|
|
827
|
+
workspacePath: params.workspacePath,
|
|
828
|
+
backendName: this.name,
|
|
829
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
830
|
+
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
831
|
+
};
|
|
832
|
+
return Ok9(session);
|
|
833
|
+
}
|
|
834
|
+
async *runTurn(session, params) {
|
|
835
|
+
const openAISession = session;
|
|
836
|
+
const messages = [];
|
|
837
|
+
if (openAISession.systemPrompt) {
|
|
838
|
+
messages.push({ role: "system", content: openAISession.systemPrompt });
|
|
839
|
+
}
|
|
840
|
+
messages.push({ role: "user", content: params.prompt });
|
|
841
|
+
let inputTokens = 0;
|
|
842
|
+
let outputTokens = 0;
|
|
843
|
+
let totalTokens = 0;
|
|
844
|
+
let cacheCreationTokens = 0;
|
|
845
|
+
let cacheReadTokens = 0;
|
|
846
|
+
try {
|
|
847
|
+
const stream = await this.client.chat.completions.create({
|
|
848
|
+
model: this.config.model,
|
|
849
|
+
messages,
|
|
850
|
+
stream: true,
|
|
851
|
+
stream_options: { include_usage: true }
|
|
852
|
+
});
|
|
853
|
+
for await (const chunk of stream) {
|
|
854
|
+
const delta = chunk.choices[0]?.delta;
|
|
855
|
+
if (delta?.content) {
|
|
856
|
+
const event = {
|
|
857
|
+
type: "text",
|
|
858
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
859
|
+
content: delta.content,
|
|
860
|
+
sessionId: session.sessionId
|
|
861
|
+
};
|
|
862
|
+
yield event;
|
|
863
|
+
}
|
|
864
|
+
if (chunk.usage) {
|
|
865
|
+
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
866
|
+
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
867
|
+
totalTokens = chunk.usage.total_tokens ?? 0;
|
|
868
|
+
const cacheUsage = this.cacheAdapter.parseCacheUsage(chunk);
|
|
869
|
+
cacheCreationTokens = cacheUsage.cacheCreationTokens;
|
|
870
|
+
cacheReadTokens = cacheUsage.cacheReadTokens;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
} catch (err) {
|
|
874
|
+
const errorMessage = err instanceof Error ? err.message : "OpenAI request failed";
|
|
875
|
+
yield {
|
|
876
|
+
type: "error",
|
|
877
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
878
|
+
content: errorMessage,
|
|
879
|
+
sessionId: session.sessionId
|
|
880
|
+
};
|
|
881
|
+
return {
|
|
882
|
+
success: false,
|
|
883
|
+
sessionId: session.sessionId,
|
|
884
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
885
|
+
error: errorMessage
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
success: true,
|
|
890
|
+
sessionId: session.sessionId,
|
|
891
|
+
usage: {
|
|
892
|
+
inputTokens,
|
|
893
|
+
outputTokens,
|
|
894
|
+
totalTokens,
|
|
895
|
+
cacheCreationTokens,
|
|
896
|
+
cacheReadTokens
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
async stopSession(_session) {
|
|
901
|
+
return Ok9(void 0);
|
|
902
|
+
}
|
|
903
|
+
async healthCheck() {
|
|
904
|
+
try {
|
|
905
|
+
await this.client.models.list();
|
|
906
|
+
return Ok9(void 0);
|
|
907
|
+
} catch (err) {
|
|
908
|
+
return Err7({
|
|
909
|
+
category: "response_error",
|
|
910
|
+
message: err instanceof Error ? err.message : "OpenAI health check failed"
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// src/agent/backends/gemini.ts
|
|
917
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
918
|
+
import {
|
|
919
|
+
Ok as Ok10,
|
|
920
|
+
Err as Err8
|
|
921
|
+
} from "@harness-engineering/types";
|
|
922
|
+
import { GeminiCacheAdapter } from "@harness-engineering/core";
|
|
923
|
+
var GeminiBackend = class {
|
|
924
|
+
name = "gemini";
|
|
925
|
+
config;
|
|
926
|
+
cacheAdapter;
|
|
927
|
+
constructor(config = {}) {
|
|
928
|
+
this.config = {
|
|
929
|
+
model: config.model ?? "gemini-2.0-flash",
|
|
930
|
+
apiKey: config.apiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY ?? ""
|
|
931
|
+
};
|
|
932
|
+
this.cacheAdapter = new GeminiCacheAdapter();
|
|
933
|
+
}
|
|
934
|
+
async startSession(params) {
|
|
935
|
+
if (!this.config.apiKey) {
|
|
936
|
+
return Err8({
|
|
937
|
+
category: "agent_not_found",
|
|
938
|
+
message: "GEMINI_API_KEY is not set"
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
const session = {
|
|
942
|
+
sessionId: `gemini-session-${Date.now()}`,
|
|
943
|
+
workspacePath: params.workspacePath,
|
|
944
|
+
backendName: this.name,
|
|
945
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
946
|
+
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
947
|
+
};
|
|
948
|
+
return Ok10(session);
|
|
949
|
+
}
|
|
950
|
+
async *runTurn(session, params) {
|
|
951
|
+
const geminiSession = session;
|
|
952
|
+
let inputTokens = 0;
|
|
953
|
+
let outputTokens = 0;
|
|
954
|
+
let totalTokens = 0;
|
|
955
|
+
let cacheCreationTokens = 0;
|
|
956
|
+
let cacheReadTokens = 0;
|
|
957
|
+
try {
|
|
958
|
+
const genAI = new GoogleGenerativeAI(this.config.apiKey);
|
|
959
|
+
const model = genAI.getGenerativeModel({
|
|
960
|
+
model: this.config.model,
|
|
961
|
+
...geminiSession.systemPrompt !== void 0 && {
|
|
962
|
+
systemInstruction: geminiSession.systemPrompt
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
const result = await model.generateContentStream(params.prompt);
|
|
966
|
+
for await (const chunk of result.stream) {
|
|
967
|
+
const text = chunk.text();
|
|
968
|
+
if (text) {
|
|
969
|
+
const event = {
|
|
970
|
+
type: "text",
|
|
971
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
972
|
+
content: text,
|
|
973
|
+
sessionId: session.sessionId
|
|
974
|
+
};
|
|
975
|
+
yield event;
|
|
976
|
+
}
|
|
977
|
+
if (chunk.usageMetadata) {
|
|
978
|
+
inputTokens = chunk.usageMetadata.promptTokenCount ?? 0;
|
|
979
|
+
outputTokens = chunk.usageMetadata.candidatesTokenCount ?? 0;
|
|
980
|
+
totalTokens = chunk.usageMetadata.totalTokenCount ?? 0;
|
|
981
|
+
const cacheUsage = this.cacheAdapter.parseCacheUsage(chunk);
|
|
982
|
+
cacheCreationTokens = cacheUsage.cacheCreationTokens;
|
|
983
|
+
cacheReadTokens = cacheUsage.cacheReadTokens;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
} catch (err) {
|
|
987
|
+
const errorMessage = err instanceof Error ? err.message : "Gemini request failed";
|
|
988
|
+
yield {
|
|
989
|
+
type: "error",
|
|
990
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
991
|
+
content: errorMessage,
|
|
992
|
+
sessionId: session.sessionId
|
|
993
|
+
};
|
|
994
|
+
return {
|
|
995
|
+
success: false,
|
|
996
|
+
sessionId: session.sessionId,
|
|
997
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
998
|
+
error: errorMessage
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
return {
|
|
1002
|
+
success: true,
|
|
1003
|
+
sessionId: session.sessionId,
|
|
1004
|
+
usage: {
|
|
1005
|
+
inputTokens,
|
|
1006
|
+
outputTokens,
|
|
1007
|
+
totalTokens,
|
|
1008
|
+
cacheCreationTokens,
|
|
1009
|
+
cacheReadTokens
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
async stopSession(_session) {
|
|
1014
|
+
return Ok10(void 0);
|
|
1015
|
+
}
|
|
1016
|
+
async healthCheck() {
|
|
1017
|
+
try {
|
|
1018
|
+
const genAI = new GoogleGenerativeAI(this.config.apiKey);
|
|
1019
|
+
genAI.getGenerativeModel({ model: this.config.model });
|
|
1020
|
+
return Ok10(void 0);
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
return Err8({
|
|
1023
|
+
category: "response_error",
|
|
1024
|
+
message: err instanceof Error ? err.message : "Gemini health check failed"
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
// src/agent/backends/anthropic.ts
|
|
1031
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
1032
|
+
import {
|
|
1033
|
+
Ok as Ok11,
|
|
1034
|
+
Err as Err9
|
|
1035
|
+
} from "@harness-engineering/types";
|
|
1036
|
+
import { AnthropicCacheAdapter } from "@harness-engineering/core";
|
|
1037
|
+
var AnthropicBackend = class {
|
|
1038
|
+
name = "anthropic";
|
|
1039
|
+
config;
|
|
1040
|
+
client;
|
|
1041
|
+
cacheAdapter;
|
|
1042
|
+
constructor(config = {}) {
|
|
1043
|
+
this.config = {
|
|
1044
|
+
model: config.model ?? "claude-sonnet-4-20250514",
|
|
1045
|
+
apiKey: config.apiKey ?? process.env.ANTHROPIC_API_KEY ?? "",
|
|
1046
|
+
maxTokens: config.maxTokens ?? 4096
|
|
1047
|
+
};
|
|
1048
|
+
this.client = new Anthropic({ apiKey: this.config.apiKey });
|
|
1049
|
+
this.cacheAdapter = new AnthropicCacheAdapter();
|
|
1050
|
+
}
|
|
1051
|
+
async startSession(params) {
|
|
1052
|
+
if (!this.config.apiKey) {
|
|
1053
|
+
return Err9({
|
|
1054
|
+
category: "agent_not_found",
|
|
1055
|
+
message: "ANTHROPIC_API_KEY is not set"
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
const session = {
|
|
1059
|
+
sessionId: `anthropic-session-${Date.now()}`,
|
|
1060
|
+
workspacePath: params.workspacePath,
|
|
1061
|
+
backendName: this.name,
|
|
1062
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1063
|
+
...params.systemPrompt !== void 0 && { systemPrompt: params.systemPrompt }
|
|
1064
|
+
};
|
|
1065
|
+
return Ok11(session);
|
|
1066
|
+
}
|
|
1067
|
+
async *runTurn(session, params) {
|
|
1068
|
+
const anthropicSession = session;
|
|
1069
|
+
const systemBlocks = anthropicSession.systemPrompt ? [
|
|
1070
|
+
this.cacheAdapter.wrapSystemBlock(
|
|
1071
|
+
anthropicSession.systemPrompt,
|
|
1072
|
+
"session"
|
|
1073
|
+
)
|
|
1074
|
+
] : void 0;
|
|
1075
|
+
try {
|
|
1076
|
+
const stream = this.client.messages.stream({
|
|
1077
|
+
model: this.config.model,
|
|
1078
|
+
max_tokens: this.config.maxTokens,
|
|
1079
|
+
...systemBlocks && { system: systemBlocks },
|
|
1080
|
+
messages: [{ role: "user", content: params.prompt }]
|
|
1081
|
+
});
|
|
1082
|
+
for await (const event of stream) {
|
|
1083
|
+
if (event.type === "content_block_delta" && "text" in event.delta) {
|
|
1084
|
+
yield {
|
|
1085
|
+
type: "text",
|
|
1086
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1087
|
+
content: event.delta.text,
|
|
1088
|
+
sessionId: session.sessionId
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
const finalMessage = await stream.finalMessage();
|
|
1093
|
+
const { input_tokens, output_tokens } = finalMessage.usage;
|
|
1094
|
+
const cacheUsage = this.cacheAdapter.parseCacheUsage(finalMessage);
|
|
1095
|
+
return {
|
|
1096
|
+
success: true,
|
|
1097
|
+
sessionId: session.sessionId,
|
|
1098
|
+
usage: {
|
|
1099
|
+
inputTokens: input_tokens,
|
|
1100
|
+
outputTokens: output_tokens,
|
|
1101
|
+
totalTokens: input_tokens + output_tokens,
|
|
1102
|
+
cacheCreationTokens: cacheUsage.cacheCreationTokens,
|
|
1103
|
+
cacheReadTokens: cacheUsage.cacheReadTokens
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
} catch (err) {
|
|
1107
|
+
const errorMessage = err instanceof Error ? err.message : "Anthropic request failed";
|
|
1108
|
+
yield {
|
|
1109
|
+
type: "error",
|
|
1110
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1111
|
+
content: errorMessage,
|
|
1112
|
+
sessionId: session.sessionId
|
|
1113
|
+
};
|
|
1114
|
+
return {
|
|
1115
|
+
success: false,
|
|
1116
|
+
sessionId: session.sessionId,
|
|
1117
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
1118
|
+
error: errorMessage
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
async stopSession(_session) {
|
|
1123
|
+
return Ok11(void 0);
|
|
1124
|
+
}
|
|
1125
|
+
async healthCheck() {
|
|
1126
|
+
if (!this.config.apiKey) {
|
|
1127
|
+
return Err9({
|
|
1128
|
+
category: "response_error",
|
|
1129
|
+
message: "ANTHROPIC_API_KEY is not set"
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
return Ok11(void 0);
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
798
1136
|
// src/prompt/renderer.ts
|
|
799
1137
|
import { Liquid } from "liquidjs";
|
|
800
1138
|
var PromptRenderer = class {
|
|
@@ -846,7 +1184,13 @@ var AgentRunner = class {
|
|
|
846
1184
|
let lastResult = {
|
|
847
1185
|
success: false,
|
|
848
1186
|
sessionId: session.sessionId,
|
|
849
|
-
usage: {
|
|
1187
|
+
usage: {
|
|
1188
|
+
inputTokens: 0,
|
|
1189
|
+
outputTokens: 0,
|
|
1190
|
+
totalTokens: 0,
|
|
1191
|
+
cacheCreationTokens: 0,
|
|
1192
|
+
cacheReadTokens: 0
|
|
1193
|
+
}
|
|
850
1194
|
};
|
|
851
1195
|
try {
|
|
852
1196
|
while (currentTurn < this.options.maxTurns) {
|
|
@@ -1031,6 +1375,21 @@ var Orchestrator = class extends EventEmitter {
|
|
|
1031
1375
|
return new MockBackend();
|
|
1032
1376
|
} else if (this.config.agent.backend === "claude") {
|
|
1033
1377
|
return new ClaudeBackend(this.config.agent.command);
|
|
1378
|
+
} else if (this.config.agent.backend === "openai") {
|
|
1379
|
+
return new OpenAIBackend({
|
|
1380
|
+
...this.config.agent.model !== void 0 && { model: this.config.agent.model },
|
|
1381
|
+
...this.config.agent.apiKey !== void 0 && { apiKey: this.config.agent.apiKey }
|
|
1382
|
+
});
|
|
1383
|
+
} else if (this.config.agent.backend === "gemini") {
|
|
1384
|
+
return new GeminiBackend({
|
|
1385
|
+
...this.config.agent.model !== void 0 && { model: this.config.agent.model },
|
|
1386
|
+
...this.config.agent.apiKey !== void 0 && { apiKey: this.config.agent.apiKey }
|
|
1387
|
+
});
|
|
1388
|
+
} else if (this.config.agent.backend === "anthropic") {
|
|
1389
|
+
return new AnthropicBackend({
|
|
1390
|
+
...this.config.agent.model !== void 0 && { model: this.config.agent.model },
|
|
1391
|
+
...this.config.agent.apiKey !== void 0 && { apiKey: this.config.agent.apiKey }
|
|
1392
|
+
});
|
|
1034
1393
|
}
|
|
1035
1394
|
throw new Error(`Unsupported agent backend: ${this.config.agent.backend}`);
|
|
1036
1395
|
}
|
|
@@ -1329,12 +1688,14 @@ var AgentsTable = ({ agents }) => {
|
|
|
1329
1688
|
/* @__PURE__ */ jsx3(Text3, { bold: true, underline: true, children: "Active Agents" }),
|
|
1330
1689
|
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", borderStyle: "single", borderColor: "gray", children: [
|
|
1331
1690
|
/* @__PURE__ */ jsx3(Box3, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Identifier" }) }),
|
|
1691
|
+
/* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Backend" }) }),
|
|
1332
1692
|
/* @__PURE__ */ jsx3(Box3, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Phase" }) }),
|
|
1333
1693
|
/* @__PURE__ */ jsx3(Box3, { width: 10, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Tokens" }) }),
|
|
1334
1694
|
/* @__PURE__ */ jsx3(Box3, { flexGrow: 1, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Message" }) })
|
|
1335
1695
|
] }),
|
|
1336
1696
|
agents.map((agent) => /* @__PURE__ */ jsxs3(Box3, { flexDirection: "row", children: [
|
|
1337
1697
|
/* @__PURE__ */ jsx3(Box3, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { children: agent.identifier }) }),
|
|
1698
|
+
/* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { color: "blue", children: agent.session?.backendName || "-" }) }),
|
|
1338
1699
|
/* @__PURE__ */ jsx3(Box3, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: agent.phase }) }),
|
|
1339
1700
|
/* @__PURE__ */ jsx3(Box3, { width: 10, children: /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: agent.session?.totalTokens || 0 }) }),
|
|
1340
1701
|
/* @__PURE__ */ jsx3(Box3, { flexGrow: 1, children: /* @__PURE__ */ jsx3(Text3, { wrap: "truncate-end", children: agent.session?.lastMessage || "-" }) })
|
|
@@ -1385,9 +1746,12 @@ function launchTUI(orchestrator) {
|
|
|
1385
1746
|
return { waitUntilExit };
|
|
1386
1747
|
}
|
|
1387
1748
|
export {
|
|
1749
|
+
AnthropicBackend,
|
|
1388
1750
|
ClaudeBackend,
|
|
1751
|
+
GeminiBackend,
|
|
1389
1752
|
LinearGraphQLStub,
|
|
1390
1753
|
MockBackend,
|
|
1754
|
+
OpenAIBackend,
|
|
1391
1755
|
Orchestrator,
|
|
1392
1756
|
PromptRenderer,
|
|
1393
1757
|
RoadmapTrackerAdapter,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/orchestrator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Orchestrator daemon for dispatching coding agents to issues",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -36,12 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/Intense-Visions/harness-engineering/tree/main/packages/orchestrator#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@anthropic-ai/sdk": "^0.87.0",
|
|
40
|
+
"@google/generative-ai": "^0.24.0",
|
|
39
41
|
"ink": "^4.4.1",
|
|
40
42
|
"liquidjs": "^10.25.3",
|
|
43
|
+
"openai": "^4.0.0",
|
|
41
44
|
"react": "^18.3.1",
|
|
42
45
|
"yaml": "^2.8.3",
|
|
43
|
-
"@harness-engineering/core": "0.21.
|
|
44
|
-
"@harness-engineering/types": "0.9.
|
|
46
|
+
"@harness-engineering/core": "0.21.4",
|
|
47
|
+
"@harness-engineering/types": "0.9.2"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@types/node": "^22.19.15",
|