@agentmeshhq/agent 0.1.8 → 0.1.9

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.
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Runner Module
3
+ * Handles model resolution, validation, and runner-specific configuration
4
+ */
5
+
6
+ import { execSync } from "node:child_process";
7
+
8
+ // ============================================================================
9
+ // Types
10
+ // ============================================================================
11
+
12
+ export type RunnerType = "opencode" | "claude" | "custom";
13
+
14
+ export interface RunnerConfig {
15
+ type: RunnerType;
16
+ command: string;
17
+ model: string;
18
+ env: Record<string, string>;
19
+ }
20
+
21
+ export interface ModelResolutionInput {
22
+ cliModel?: string;
23
+ agentModel?: string;
24
+ defaultModel: string;
25
+ command: string;
26
+ }
27
+
28
+ // ============================================================================
29
+ // Runner Detection
30
+ // ============================================================================
31
+
32
+ /**
33
+ * Detects the runner type from the command
34
+ */
35
+ export function detectRunner(command: string): RunnerType {
36
+ const cmd = command.toLowerCase().trim();
37
+
38
+ if (cmd === "opencode" || cmd.startsWith("opencode ")) {
39
+ return "opencode";
40
+ }
41
+
42
+ if (cmd === "claude" || cmd.startsWith("claude ")) {
43
+ return "claude";
44
+ }
45
+
46
+ return "custom";
47
+ }
48
+
49
+ // ============================================================================
50
+ // Model Resolution
51
+ // ============================================================================
52
+
53
+ /**
54
+ * Resolves the effective model from CLI > agent config > defaults
55
+ */
56
+ export function resolveModel(input: ModelResolutionInput): string {
57
+ // Priority: CLI flag > agent config > default
58
+ return input.cliModel || input.agentModel || input.defaultModel;
59
+ }
60
+
61
+ // ============================================================================
62
+ // OpenCode Integration
63
+ // ============================================================================
64
+
65
+ let cachedOpenCodeModels: string[] | null = null;
66
+
67
+ /**
68
+ * Gets available OpenCode models (cached)
69
+ */
70
+ export function getOpenCodeModels(): string[] {
71
+ if (cachedOpenCodeModels) {
72
+ return cachedOpenCodeModels;
73
+ }
74
+
75
+ try {
76
+ const output = execSync("opencode models 2>/dev/null", {
77
+ encoding: "utf-8",
78
+ timeout: 10000,
79
+ });
80
+
81
+ cachedOpenCodeModels = output
82
+ .split("\n")
83
+ .map((line) => line.trim())
84
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
85
+
86
+ return cachedOpenCodeModels;
87
+ } catch {
88
+ // OpenCode not available or failed
89
+ return [];
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Validates that a model is available in OpenCode
95
+ */
96
+ export function validateOpenCodeModel(model: string): { valid: boolean; error?: string } {
97
+ const models = getOpenCodeModels();
98
+
99
+ // If we couldn't get models list, allow any (graceful degradation)
100
+ if (models.length === 0) {
101
+ return { valid: true };
102
+ }
103
+
104
+ if (models.includes(model)) {
105
+ return { valid: true };
106
+ }
107
+
108
+ // Check for partial match (e.g., "claude-sonnet-4" matches "anthropic/claude-sonnet-4")
109
+ const partialMatch = models.find(
110
+ (m) => m.endsWith(`/${model}`) || m === model || m.split("/").pop() === model,
111
+ );
112
+
113
+ if (partialMatch) {
114
+ return { valid: true };
115
+ }
116
+
117
+ return {
118
+ valid: false,
119
+ error: `Model "${model}" not found in OpenCode. Run 'opencode models' to see available models.`,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Normalizes a model name for OpenCode
125
+ * e.g., "claude-sonnet-4" -> "anthropic/claude-sonnet-4" if that's what OpenCode expects
126
+ */
127
+ export function normalizeOpenCodeModel(model: string): string {
128
+ const models = getOpenCodeModels();
129
+
130
+ // Direct match
131
+ if (models.includes(model)) {
132
+ return model;
133
+ }
134
+
135
+ // Try to find full path version
136
+ const fullPath = models.find((m) => m.endsWith(`/${model}`) || m.split("/").pop() === model);
137
+
138
+ return fullPath || model;
139
+ }
140
+
141
+ // ============================================================================
142
+ // Runner Configuration
143
+ // ============================================================================
144
+
145
+ /**
146
+ * Builds the complete runner configuration including environment variables
147
+ */
148
+ export function buildRunnerConfig(input: ModelResolutionInput): RunnerConfig {
149
+ const runnerType = detectRunner(input.command);
150
+ const model = resolveModel(input);
151
+ const env: Record<string, string> = {};
152
+
153
+ switch (runnerType) {
154
+ case "opencode": {
155
+ // Validate model for OpenCode
156
+ const validation = validateOpenCodeModel(model);
157
+ if (!validation.valid) {
158
+ console.warn(`Warning: ${validation.error}`);
159
+ }
160
+
161
+ // Normalize and set OPENCODE_MODEL
162
+ const normalizedModel = normalizeOpenCodeModel(model);
163
+ env.OPENCODE_MODEL = normalizedModel;
164
+ break;
165
+ }
166
+
167
+ case "claude": {
168
+ // Claude CLI uses ANTHROPIC_MODEL or similar
169
+ // For now, just pass the model - Claude CLI will handle it
170
+ env.CLAUDE_MODEL = model;
171
+ break;
172
+ }
173
+
174
+ case "custom":
175
+ // Custom runners don't get automatic model env
176
+ // User is responsible for configuring their tool
177
+ break;
178
+ }
179
+
180
+ return {
181
+ type: runnerType,
182
+ command: input.command,
183
+ model,
184
+ env,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Gets a human-readable runner name
190
+ */
191
+ export function getRunnerDisplayName(runnerType: RunnerType): string {
192
+ switch (runnerType) {
193
+ case "opencode":
194
+ return "OpenCode";
195
+ case "claude":
196
+ return "Claude CLI";
197
+ case "custom":
198
+ return "Custom";
199
+ }
200
+ }
package/src/core/tmux.ts CHANGED
@@ -18,6 +18,9 @@ export function sessionExists(sessionName: string): boolean {
18
18
  export interface SessionEnv {
19
19
  AGENT_TOKEN?: string;
20
20
  AGENTMESH_AGENT_ID?: string;
21
+ OPENCODE_MODEL?: string;
22
+ CLAUDE_MODEL?: string;
23
+ [key: string]: string | undefined;
21
24
  }
22
25
 
23
26
  export function createSession(
@@ -34,17 +37,36 @@ export function createSession(
34
37
  }
35
38
 
36
39
  try {
40
+ // Build environment prefix for the command
41
+ // This ensures env vars are set BEFORE the process starts
42
+ let envPrefix = "";
43
+ if (env) {
44
+ const envParts: string[] = [];
45
+ for (const [key, value] of Object.entries(env)) {
46
+ if (value !== undefined && value !== "") {
47
+ // Escape special characters in value
48
+ const escapedValue = value.replace(/"/g, '\\"');
49
+ envParts.push(`${key}="${escapedValue}"`);
50
+ }
51
+ }
52
+ if (envParts.length > 0) {
53
+ envPrefix = envParts.join(" ") + " ";
54
+ }
55
+ }
56
+
57
+ const fullCommand = `${envPrefix}${command}`;
58
+
37
59
  const args = ["new-session", "-d", "-s", sessionName];
38
60
 
39
61
  if (workdir) {
40
62
  args.push("-c", workdir);
41
63
  }
42
64
 
43
- args.push(command);
65
+ args.push(fullCommand);
44
66
 
45
67
  execSync(`tmux ${args.join(" ")}`);
46
68
 
47
- // Inject environment variables after session creation
69
+ // Also set session environment for any subsequent processes/refreshes
48
70
  if (env) {
49
71
  setSessionEnvironment(sessionName, env);
50
72
  }
@@ -58,13 +80,10 @@ export function createSession(
58
80
 
59
81
  export function setSessionEnvironment(sessionName: string, env: SessionEnv): boolean {
60
82
  try {
61
- if (env.AGENT_TOKEN) {
62
- execSync(`tmux set-environment -t "${sessionName}" AGENT_TOKEN "${env.AGENT_TOKEN}"`);
63
- }
64
- if (env.AGENTMESH_AGENT_ID) {
65
- execSync(
66
- `tmux set-environment -t "${sessionName}" AGENTMESH_AGENT_ID "${env.AGENTMESH_AGENT_ID}"`,
67
- );
83
+ for (const [key, value] of Object.entries(env)) {
84
+ if (value !== undefined && value !== "") {
85
+ execSync(`tmux set-environment -t "${sessionName}" ${key} "${value}"`);
86
+ }
68
87
  }
69
88
  return true;
70
89
  } catch (error) {