@hilbras/omninode 0.2.0 → 0.4.0

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.js CHANGED
@@ -123,6 +123,8 @@ var agentConfigSchema = z.object({
123
123
  args: z.array(z.string()).optional(),
124
124
  cwd: z.string().optional(),
125
125
  env: z.record(z.string()).optional(),
126
+ input_mode: z.enum(["stdin", "arg", "protocol"]).default("stdin"),
127
+ timeout_ms: z.number().int().positive().optional(),
126
128
  metadata: z.record(z.unknown()).optional()
127
129
  }).strict();
128
130
  var roleConfigSchema = z.object({
@@ -234,6 +236,8 @@ function toAgentConfig(raw) {
234
236
  args: raw.args,
235
237
  cwd: raw.cwd,
236
238
  env: raw.env,
239
+ inputMode: raw.input_mode,
240
+ timeoutMs: raw.timeout_ms,
237
241
  metadata: raw.metadata
238
242
  };
239
243
  }
@@ -482,6 +486,49 @@ function inferHealth(error) {
482
486
  return status !== void 0 && status >= 500 ? "degraded" : "unreachable";
483
487
  }
484
488
 
489
+ // src/providers/omnihilbras/index.ts
490
+ var OmniHilbrasProvider = class extends OpenAICompatibleProvider {
491
+ /**
492
+ * Run the full connect flow. Throws on authentication or transport failure
493
+ * (use healthCheck() for a non-throwing probe).
494
+ */
495
+ async connect(registry) {
496
+ const apiKey = resolveApiKey(this.config);
497
+ const discovered = await this.listModels();
498
+ const seen = /* @__PURE__ */ new Set();
499
+ const models = [];
500
+ const duplicates = [];
501
+ for (const model of discovered) {
502
+ if (seen.has(model.id)) {
503
+ duplicates.push(model.id);
504
+ continue;
505
+ }
506
+ seen.add(model.id);
507
+ models.push({
508
+ ...model,
509
+ provider: this.config.name,
510
+ status: "available",
511
+ metadata: { ...model.metadata, gateway: "omnihilbras" }
512
+ });
513
+ }
514
+ if (registry) {
515
+ for (const model of models) registry.register(model);
516
+ }
517
+ const message = `Authenticated${apiKey ? " with API key" : " (no API key configured)"}; validated ${models.length} unique model(s)` + (duplicates.length > 0 ? `, skipped ${duplicates.length} duplicate(s)` : "") + ".";
518
+ const status = {
519
+ name: this.config.name,
520
+ type: this.config.type,
521
+ baseUrl: this.config.baseUrl,
522
+ health: "healthy",
523
+ connected: true,
524
+ modelCount: models.length,
525
+ lastCheckedAt: (/* @__PURE__ */ new Date()).toISOString(),
526
+ message
527
+ };
528
+ return { status, models, duplicates, registered: registry ? models.length : 0 };
529
+ }
530
+ };
531
+
485
532
  // src/providers/factory.ts
486
533
  var OPENAI_COMPATIBLE_TYPES = [
487
534
  "openai-compatible",
@@ -490,15 +537,12 @@ var OPENAI_COMPATIBLE_TYPES = [
490
537
  "custom"
491
538
  ];
492
539
  function createProvider(config) {
540
+ if (config.type === "omnihilbras") {
541
+ return new OmniHilbrasProvider(config);
542
+ }
493
543
  if (OPENAI_COMPATIBLE_TYPES.includes(config.type)) {
494
544
  return new OpenAICompatibleProvider(config);
495
545
  }
496
- if (config.type === "omnihilbras") {
497
- throw new OmniNodeError(
498
- "NOT_IMPLEMENTED",
499
- `Provider type "omnihilbras" is planned for Phase 2 (OmniHilbras Integration). If your OmniHilbras endpoint is OpenAI-compatible, use type "openai-compatible" for now.`
500
- );
501
- }
502
546
  throw new OmniNodeError("NOT_IMPLEMENTED", `Provider type "${config.type}" has no adapter yet.`);
503
547
  }
504
548
 
@@ -551,10 +595,290 @@ var ModelRegistry = class {
551
595
  }
552
596
  };
553
597
 
598
+ // src/agents/process/index.ts
599
+ import { spawn } from "child_process";
600
+ var DEFAULT_TIMEOUT_MS2 = 6e5;
601
+ var KILL_GRACE_MS = 5e3;
602
+ function composeTaskText(input) {
603
+ const sections = [`# Objective
604
+ ${input.objective}`];
605
+ if (input.role) {
606
+ const lines = ["# Role", `${input.role.name} (${input.role.id})`];
607
+ if (input.role.responsibilities.length > 0) {
608
+ lines.push("", "Responsibilities:", ...input.role.responsibilities.map((r) => `- ${r}`));
609
+ }
610
+ if (input.role.expectedOutputs.length > 0) {
611
+ lines.push("", "Expected outputs:", ...input.role.expectedOutputs.map((o) => `- ${o}`));
612
+ }
613
+ sections.push(lines.join("\n"));
614
+ }
615
+ if (input.context) sections.push(`# Context
616
+ ${input.context}`);
617
+ if (input.instruction) sections.push(`# Instruction
618
+ ${input.instruction}`);
619
+ return sections.join("\n\n");
620
+ }
621
+ function buildProtocolMessages(input, agentName) {
622
+ const meta = {
623
+ taskId: input.taskId,
624
+ agent: agentName,
625
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
626
+ };
627
+ const messages = [
628
+ { type: "TASK", payload: { taskId: input.taskId, objective: input.objective } }
629
+ ];
630
+ if (input.role) messages.push({ type: "ROLE", payload: input.role });
631
+ if (input.context) messages.push({ type: "CONTEXT", payload: { body: input.context } });
632
+ if (input.instruction) messages.push({ type: "INSTRUCTION", payload: { text: input.instruction } });
633
+ return messages.map((message) => JSON.stringify({ meta, message }));
634
+ }
635
+ var ProcessAgent = class {
636
+ config;
637
+ info;
638
+ log;
639
+ running = /* @__PURE__ */ new Map();
640
+ constructor(config, log = logger) {
641
+ if (!config.command) {
642
+ throw new AgentError("AGENT_NOT_FOUND", `Agent "${config.name}" has no command configured.`);
643
+ }
644
+ this.config = config;
645
+ this.log = log.child({ agent: config.name });
646
+ this.info = {
647
+ name: config.name,
648
+ integration: "process",
649
+ command: config.command,
650
+ cwd: config.cwd,
651
+ status: "ready",
652
+ capabilities: [config.inputMode ?? "stdin"]
653
+ };
654
+ }
655
+ async run(input) {
656
+ const command = this.config.command;
657
+ if (!command) {
658
+ throw new AgentError("AGENT_NOT_FOUND", `Agent "${this.config.name}" has no command configured.`);
659
+ }
660
+ const mode = this.config.inputMode ?? "stdin";
661
+ const timeoutMs = this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
662
+ return new Promise((resolve, reject) => {
663
+ const child = spawn(
664
+ command,
665
+ [...this.config.args ?? [], ...mode === "arg" ? [composeTaskText(input)] : []],
666
+ {
667
+ cwd: this.config.cwd,
668
+ env: { ...process.env, ...this.config.env },
669
+ stdio: ["pipe", "pipe", "pipe"]
670
+ }
671
+ );
672
+ this.running.set(input.taskId, child);
673
+ let stdout = "";
674
+ let stderr = "";
675
+ let timedOut = false;
676
+ let settled = false;
677
+ const timer = setTimeout(() => {
678
+ timedOut = true;
679
+ this.log.warn(`Task ${input.taskId} exceeded ${timeoutMs}ms \u2014 killing process.`);
680
+ child.kill("SIGTERM");
681
+ setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS).unref();
682
+ }, timeoutMs);
683
+ const settle = (finish) => {
684
+ if (settled) return;
685
+ settled = true;
686
+ clearTimeout(timer);
687
+ this.running.delete(input.taskId);
688
+ finish();
689
+ };
690
+ child.stdout?.on("data", (chunk) => {
691
+ stdout += chunk.toString();
692
+ });
693
+ child.stderr?.on("data", (chunk) => {
694
+ stderr += chunk.toString();
695
+ });
696
+ child.stdin?.on("error", () => {
697
+ });
698
+ if (mode !== "arg") {
699
+ const payload = mode === "protocol" ? buildProtocolMessages(input, this.config.name).join("\n") + "\n" : composeTaskText(input) + "\n";
700
+ child.stdin?.write(payload);
701
+ child.stdin?.end();
702
+ }
703
+ child.on("error", (error) => {
704
+ settle(() => {
705
+ if (error.code === "ENOENT") {
706
+ reject(
707
+ new AgentError(
708
+ "AGENT_NOT_FOUND",
709
+ `Agent "${this.config.name}": command "${command}" was not found. Is it installed and on PATH?`,
710
+ { cause: error }
711
+ )
712
+ );
713
+ return;
714
+ }
715
+ reject(
716
+ new AgentError("AGENT_FAILED", `Agent "${this.config.name}" failed to start: ${error.message}.`, {
717
+ cause: error
718
+ })
719
+ );
720
+ });
721
+ });
722
+ child.on("close", (code, signal) => {
723
+ settle(() => {
724
+ if (timedOut) {
725
+ reject(
726
+ new AgentError(
727
+ "AGENT_TIMEOUT",
728
+ `Agent "${this.config.name}" timed out after ${timeoutMs}ms and was killed.`,
729
+ { details: { taskId: input.taskId, stdout: stdout.slice(0, 4e3), stderr: stderr.slice(0, 2e3) } }
730
+ )
731
+ );
732
+ return;
733
+ }
734
+ const logs = stderr.trim().length > 0 ? stderr.split("\n") : void 0;
735
+ if (mode === "protocol") {
736
+ try {
737
+ resolve(
738
+ interpretProtocolRun({
739
+ input,
740
+ agentName: this.config.name,
741
+ stdout,
742
+ exitCode: code,
743
+ logs
744
+ })
745
+ );
746
+ } catch (error) {
747
+ reject(error);
748
+ }
749
+ return;
750
+ }
751
+ const trimmed = stdout.trim();
752
+ resolve({
753
+ taskId: input.taskId,
754
+ status: code === 0 ? "completed" : "failed",
755
+ summary: trimmed.length > 0 ? firstLine(trimmed) : void 0,
756
+ rawOutput: trimmed.length > 0 ? trimmed : void 0,
757
+ exitCode: code ?? void 0,
758
+ logs,
759
+ error: code === 0 ? void 0 : `Process exited with code ${code ?? "unknown"}${signal ? ` (signal: ${signal})` : ""}`
760
+ });
761
+ });
762
+ });
763
+ });
764
+ }
765
+ /** Ask the agent to stop current work (SIGTERM). */
766
+ async cancel(taskId) {
767
+ const child = this.running.get(taskId);
768
+ if (child) child.kill("SIGTERM");
769
+ }
770
+ runningTaskIds() {
771
+ return [...this.running.keys()];
772
+ }
773
+ };
774
+ function interpretProtocolRun({ input, agentName, stdout, exitCode, logs }) {
775
+ const reports = [];
776
+ let summary;
777
+ let error;
778
+ let sawProtocolMessage = false;
779
+ for (const line of stdout.split("\n")) {
780
+ const trimmed2 = line.trim();
781
+ if (trimmed2.length === 0) continue;
782
+ const message = parseAgentMessage(trimmed2);
783
+ if (!message) continue;
784
+ sawProtocolMessage = true;
785
+ if (message.type === "REPORT") {
786
+ const report = normalizeReport(message.payload, input.taskId, agentName, reports.length);
787
+ if (report) reports.push(report);
788
+ } else if (message.type === "COMPLETION") {
789
+ const payload = message.payload;
790
+ if (typeof payload?.summary === "string") summary = payload.summary;
791
+ } else if (message.type === "ERROR") {
792
+ const payload = message.payload;
793
+ const detail = typeof payload?.message === "string" ? payload.message : "unknown error";
794
+ const code = typeof payload?.code === "string" ? ` [${payload.code}]` : "";
795
+ error = `Agent error${code}: ${detail}`;
796
+ }
797
+ }
798
+ if (!sawProtocolMessage && exitCode === 0) {
799
+ throw new ProtocolError(
800
+ `Agent "${agentName}" produced no protocol messages on stdout (input mode "protocol").`,
801
+ { details: { taskId: input.taskId, stdout: stdout.slice(0, 2e3) } }
802
+ );
803
+ }
804
+ const trimmed = stdout.trim();
805
+ return {
806
+ taskId: input.taskId,
807
+ status: error !== void 0 || exitCode !== 0 ? "failed" : "completed",
808
+ summary: summary ?? reports[0]?.summary,
809
+ rawOutput: trimmed.length > 0 ? trimmed : void 0,
810
+ exitCode: exitCode ?? void 0,
811
+ logs,
812
+ reports: reports.length > 0 ? reports : void 0,
813
+ error: error ?? (exitCode !== 0 ? `Process exited with code ${exitCode ?? "unknown"}` : void 0)
814
+ };
815
+ }
816
+ function parseAgentMessage(line) {
817
+ try {
818
+ const parsed = JSON.parse(line);
819
+ if (typeof parsed.type === "string") {
820
+ return { type: parsed.type, payload: parsed.payload };
821
+ }
822
+ const inner = parsed.message;
823
+ if (inner && typeof inner.type === "string") {
824
+ return { type: inner.type, payload: inner.payload };
825
+ }
826
+ } catch {
827
+ }
828
+ return void 0;
829
+ }
830
+ function normalizeReport(payload, taskId, agentName, index) {
831
+ if (payload === null || typeof payload !== "object") return void 0;
832
+ const raw = payload;
833
+ if (typeof raw.summary !== "string") return void 0;
834
+ return {
835
+ id: typeof raw.id === "string" ? raw.id : `${taskId}-report-${index}`,
836
+ taskId: typeof raw.taskId === "string" ? raw.taskId : taskId,
837
+ agent: typeof raw.agent === "string" ? raw.agent : agentName,
838
+ summary: raw.summary,
839
+ findings: Array.isArray(raw.findings) ? raw.findings : [],
840
+ recommendations: Array.isArray(raw.recommendations) ? raw.recommendations : [],
841
+ createdAt: typeof raw.createdAt === "string" ? raw.createdAt : (/* @__PURE__ */ new Date()).toISOString()
842
+ };
843
+ }
844
+ function firstLine(text, maxLength = 200) {
845
+ const line = text.split("\n", 1)[0] ?? text;
846
+ return line.length > maxLength ? `${line.slice(0, maxLength)}\u2026` : line;
847
+ }
848
+
849
+ // src/agents/factory.ts
850
+ function createAgent(config) {
851
+ if (config.integration === "process") {
852
+ return new ProcessAgent(config);
853
+ }
854
+ if (config.integration === "native") {
855
+ throw new OmniNodeError(
856
+ "NOT_IMPLEMENTED",
857
+ `Native agent adapters are planned for a later phase. Use input over stdin/stdout (type "cli") for "${config.name}" for now.`
858
+ );
859
+ }
860
+ throw new OmniNodeError("NOT_IMPLEMENTED", `Agent integration "${config.integration}" has no adapter yet.`);
861
+ }
862
+
863
+ // src/agents/registry.ts
864
+ var AgentRegistry = class {
865
+ agents = /* @__PURE__ */ new Map();
866
+ register(agent) {
867
+ this.agents.set(agent.info.name, agent);
868
+ }
869
+ get(name) {
870
+ return this.agents.get(name);
871
+ }
872
+ list() {
873
+ return [...this.agents.values()].sort((a, b) => a.info.name.localeCompare(b.info.name));
874
+ }
875
+ };
876
+
554
877
  // src/version.ts
555
- var OMNINODE_VERSION = "0.2.0";
878
+ var OMNINODE_VERSION = "0.4.0";
556
879
  export {
557
880
  AgentError,
881
+ AgentRegistry,
558
882
  ConfigError,
559
883
  ConsoleLogSink,
560
884
  Logger,
@@ -562,15 +886,19 @@ export {
562
886
  ModelRegistry,
563
887
  OMNINODE_VERSION,
564
888
  OPENAI_COMPATIBLE_TYPES,
889
+ OmniHilbrasProvider,
565
890
  OmniNodeError,
566
891
  OpenAICompatibleProvider,
567
892
  PipelineError,
893
+ ProcessAgent,
568
894
  ProtocolError,
569
895
  ProviderError,
570
896
  TaskError,
571
897
  agentConfigSchema,
572
898
  appConfigSchema,
573
899
  authHeaders,
900
+ composeTaskText,
901
+ createAgent,
574
902
  createProvider,
575
903
  defaultProjectConfigYaml,
576
904
  expandEnvRefs,