@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/README.md +41 -5
- package/dist/cli/index.js +492 -86
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +113 -34
- package/dist/index.js +335 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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,
|