@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 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 import_core3 = require("@harness-engineering/core");
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: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
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 import_core2 = require("@harness-engineering/core");
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, import_core2.scanForInjection)(content);
1008
- const findings = (0, import_core2.mapInjectionFindings)(injectionFindings);
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, import_core2.mapSecurityFindings)(secFindings, findings));
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, import_core2.computeOverallSeverity)(findings)
1352
+ overallSeverity: (0, import_core5.computeOverallSeverity)(findings)
1015
1353
  };
1016
1354
  }
1017
1355
  async function scanWorkspaceConfig(workspacePath) {
1018
- const scanner = new import_core2.SecurityScanner((0, import_core2.parseSecurityConfig)({}));
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, import_core2.computeScanExitCode)(results), results };
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, import_core3.writeTaint)(
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: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
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.6",
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.2",
44
- "@harness-engineering/types": "0.9.1"
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",