@orchagent/cli 0.3.43 → 0.3.45
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/commands/call.js +14 -568
- package/dist/commands/info.js +1 -1
- package/dist/commands/init.js +15 -30
- package/dist/commands/install.js +4 -4
- package/dist/commands/pricing.js +1 -1
- package/dist/commands/publish.js +10 -0
- package/dist/commands/run.js +663 -271
- package/dist/commands/search.js +1 -1
- package/dist/commands/seller.js +5 -5
- package/dist/commands/skill.js +4 -4
- package/dist/index.js +1 -2
- package/dist/lib/errors.js +1 -1
- package/package.json +1 -1
package/dist/commands/call.js
CHANGED
|
@@ -1,577 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.registerCallCommand = registerCallCommand;
|
|
7
|
-
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const config_1 = require("../lib/config");
|
|
10
|
-
const api_1 = require("../lib/api");
|
|
11
|
-
const errors_1 = require("../lib/errors");
|
|
12
|
-
const output_1 = require("../lib/output");
|
|
13
|
-
const spinner_1 = require("../lib/spinner");
|
|
14
|
-
const llm_1 = require("../lib/llm");
|
|
15
|
-
const analytics_1 = require("../lib/analytics");
|
|
16
|
-
const pricing_1 = require("../lib/pricing");
|
|
17
|
-
const DEFAULT_VERSION = 'latest';
|
|
18
|
-
// Well-known field names for file content in prompt agent schemas (priority order)
|
|
19
|
-
const CONTENT_FIELD_NAMES = ['code', 'content', 'text', 'source', 'input', 'file_content', 'body'];
|
|
20
|
-
// Keys that might indicate local file path references in JSON payloads
|
|
21
|
-
const LOCAL_PATH_KEYS = ['path', 'directory', 'file', 'filepath', 'dir', 'folder', 'local'];
|
|
22
4
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
5
|
+
* Deprecated: The 'call' command has been merged into 'run'.
|
|
6
|
+
* Cloud execution is now the default behavior of 'orch run'.
|
|
7
|
+
* This file provides a thin alias that prints a deprecation notice and exits.
|
|
25
8
|
*/
|
|
26
|
-
function findLocalPathKey(obj) {
|
|
27
|
-
if (typeof obj !== 'object' || obj === null) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
const keys = Object.keys(obj);
|
|
31
|
-
for (const key of keys) {
|
|
32
|
-
if (LOCAL_PATH_KEYS.includes(key.toLowerCase())) {
|
|
33
|
-
return key;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Emit a warning to stderr if the payload contains local path references.
|
|
40
|
-
*/
|
|
41
|
-
function warnIfLocalPathReference(jsonBody) {
|
|
42
|
-
try {
|
|
43
|
-
const parsed = JSON.parse(jsonBody);
|
|
44
|
-
const pathKey = findLocalPathKey(parsed);
|
|
45
|
-
if (pathKey) {
|
|
46
|
-
process.stderr.write(`Warning: Your payload contains a local path reference ('${pathKey}').\n` +
|
|
47
|
-
`Remote agents cannot access your local filesystem. The path will be interpreted\n` +
|
|
48
|
-
`by the server, not your local machine.\n\n` +
|
|
49
|
-
`Tip: Use 'orchagent run <agent>' instead to execute locally with filesystem access.\n\n`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// If parsing fails, skip the warning (the actual error will be thrown later)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Infer the best JSON field name for file content based on the agent's input schema.
|
|
58
|
-
* Returns the field name to use, or 'content' as a safe default.
|
|
59
|
-
*/
|
|
60
|
-
function inferFileField(inputSchema) {
|
|
61
|
-
if (!inputSchema || typeof inputSchema !== 'object')
|
|
62
|
-
return 'content';
|
|
63
|
-
const props = inputSchema.properties;
|
|
64
|
-
if (!props || typeof props !== 'object')
|
|
65
|
-
return 'content';
|
|
66
|
-
const properties = props;
|
|
67
|
-
// Check for well-known field names in priority order
|
|
68
|
-
for (const field of CONTENT_FIELD_NAMES) {
|
|
69
|
-
if (properties[field] && properties[field].type === 'string')
|
|
70
|
-
return field;
|
|
71
|
-
}
|
|
72
|
-
// If there's exactly one required string property, use that
|
|
73
|
-
const required = (inputSchema.required ?? []);
|
|
74
|
-
const stringProps = Object.entries(properties)
|
|
75
|
-
.filter(([, v]) => v.type === 'string')
|
|
76
|
-
.map(([k]) => k);
|
|
77
|
-
if (stringProps.length === 1)
|
|
78
|
-
return stringProps[0];
|
|
79
|
-
const requiredStrings = stringProps.filter(k => required.includes(k));
|
|
80
|
-
if (requiredStrings.length === 1)
|
|
81
|
-
return requiredStrings[0];
|
|
82
|
-
return 'content';
|
|
83
|
-
}
|
|
84
|
-
function parseAgentRef(value) {
|
|
85
|
-
const [ref, versionPart] = value.split('@');
|
|
86
|
-
const version = versionPart?.trim() || DEFAULT_VERSION;
|
|
87
|
-
const segments = ref.split('/');
|
|
88
|
-
if (segments.length === 1) {
|
|
89
|
-
return { agent: segments[0], version };
|
|
90
|
-
}
|
|
91
|
-
if (segments.length === 2) {
|
|
92
|
-
return { org: segments[0], agent: segments[1], version };
|
|
93
|
-
}
|
|
94
|
-
throw new errors_1.CliError('Invalid agent reference. Use org/agent or agent format.');
|
|
95
|
-
}
|
|
96
|
-
async function readStdin() {
|
|
97
|
-
if (process.stdin.isTTY)
|
|
98
|
-
return null;
|
|
99
|
-
const chunks = [];
|
|
100
|
-
for await (const chunk of process.stdin) {
|
|
101
|
-
chunks.push(Buffer.from(chunk));
|
|
102
|
-
}
|
|
103
|
-
if (!chunks.length)
|
|
104
|
-
return null;
|
|
105
|
-
return Buffer.concat(chunks);
|
|
106
|
-
}
|
|
107
|
-
async function buildMultipartBody(filePaths, metadata) {
|
|
108
|
-
if (!filePaths || filePaths.length === 0) {
|
|
109
|
-
const stdinData = await readStdin();
|
|
110
|
-
if (stdinData) {
|
|
111
|
-
const form = new FormData();
|
|
112
|
-
form.append('files[]', new Blob([new Uint8Array(stdinData)]), 'stdin');
|
|
113
|
-
if (metadata) {
|
|
114
|
-
form.append('metadata', metadata);
|
|
115
|
-
}
|
|
116
|
-
return { body: form, sourceLabel: 'stdin' };
|
|
117
|
-
}
|
|
118
|
-
if (metadata) {
|
|
119
|
-
const form = new FormData();
|
|
120
|
-
form.append('metadata', metadata);
|
|
121
|
-
return { body: form, sourceLabel: 'metadata' };
|
|
122
|
-
}
|
|
123
|
-
return {};
|
|
124
|
-
}
|
|
125
|
-
const form = new FormData();
|
|
126
|
-
for (const filePath of filePaths) {
|
|
127
|
-
const buffer = await promises_1.default.readFile(filePath);
|
|
128
|
-
const filename = path_1.default.basename(filePath);
|
|
129
|
-
form.append('files[]', new Blob([new Uint8Array(buffer)]), filename);
|
|
130
|
-
}
|
|
131
|
-
if (metadata) {
|
|
132
|
-
form.append('metadata', metadata);
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
body: form,
|
|
136
|
-
sourceLabel: filePaths.length === 1 ? filePaths[0] : `${filePaths.length} files`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
async function resolveJsonBody(input) {
|
|
140
|
-
let raw = input;
|
|
141
|
-
if (input.startsWith('@')) {
|
|
142
|
-
const source = input.slice(1);
|
|
143
|
-
if (!source) {
|
|
144
|
-
throw new errors_1.CliError('Invalid JSON input. Use a JSON string or @file.');
|
|
145
|
-
}
|
|
146
|
-
if (source === '-') {
|
|
147
|
-
const stdinData = await readStdin();
|
|
148
|
-
if (!stdinData) {
|
|
149
|
-
throw new errors_1.CliError('No stdin provided for JSON input.');
|
|
150
|
-
}
|
|
151
|
-
raw = stdinData.toString('utf8');
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
raw = await promises_1.default.readFile(source, 'utf8');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
return JSON.stringify(JSON.parse(raw));
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
throw (0, errors_1.jsonInputError)('data');
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
9
|
function registerCallCommand(program) {
|
|
165
10
|
program
|
|
166
|
-
.command('call
|
|
167
|
-
.description('
|
|
168
|
-
.
|
|
169
|
-
.
|
|
170
|
-
.
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
.
|
|
177
|
-
.option('--skills <skills>', 'Add skills (comma-separated)')
|
|
178
|
-
.option('--skills-only <skills>', 'Use only these skills')
|
|
179
|
-
.option('--no-skills', 'Ignore default skills')
|
|
180
|
-
.option('--file <path...>', 'File(s) to upload (can specify multiple)')
|
|
181
|
-
.option('--file-field <field>', 'Schema field name for file content (prompt agents)')
|
|
182
|
-
.option('--metadata <json>', 'JSON metadata to send with files')
|
|
183
|
-
.addHelpText('after', `
|
|
184
|
-
Examples:
|
|
185
|
-
orch call orchagent/invoice-scanner invoice.pdf
|
|
186
|
-
orch call orchagent/useeffect-checker --file src/App.tsx
|
|
187
|
-
orch call orchagent/useeffect-checker --file src/App.tsx --file-field code
|
|
188
|
-
orch call orchagent/leak-finder --data '{"repo_url": "https://github.com/org/repo"}'
|
|
189
|
-
cat input.json | orch call acme/agent --data @-
|
|
190
|
-
orch call acme/image-processor photo.jpg --output result.png
|
|
191
|
-
|
|
192
|
-
Note: Use 'call' for server-side execution (requires login), 'run' for local execution.
|
|
193
|
-
|
|
194
|
-
File handling:
|
|
195
|
-
For prompt agents, file content is read and sent as JSON mapped to the agent's
|
|
196
|
-
input schema. Use --file-field to specify the field name (auto-detected by default).
|
|
197
|
-
For tools, files are uploaded as multipart form data.
|
|
198
|
-
|
|
199
|
-
Important: Remote agents cannot access your local filesystem. If your --data payload
|
|
200
|
-
contains keys like 'path', 'directory', 'file', etc., those values will be interpreted
|
|
201
|
-
by the server, not your local machine. To send local files, use the positional file
|
|
202
|
-
argument or --file option instead.
|
|
203
|
-
|
|
204
|
-
Paid Agents:
|
|
205
|
-
Paid agents charge per call and deduct from your prepaid credits.
|
|
206
|
-
Check your balance: orch billing balance
|
|
207
|
-
Add credits: orch billing add 5
|
|
208
|
-
|
|
209
|
-
Same-author calls are FREE - you won't be charged for calling your own agents.
|
|
210
|
-
`)
|
|
211
|
-
.action(async (agentRef, file, options) => {
|
|
212
|
-
// Merge --input alias into --data
|
|
213
|
-
const dataValue = options.data || options.input;
|
|
214
|
-
options.data = dataValue;
|
|
215
|
-
const resolved = await (0, config_1.getResolvedConfig)();
|
|
216
|
-
if (!resolved.apiKey) {
|
|
217
|
-
throw new errors_1.CliError('Missing API key. Run `orchagent login` first.');
|
|
218
|
-
}
|
|
219
|
-
const parsed = parseAgentRef(agentRef);
|
|
220
|
-
const configFile = await (0, config_1.loadConfig)();
|
|
221
|
-
const org = parsed.org ?? configFile.workspace ?? resolved.defaultOrg;
|
|
222
|
-
if (!org) {
|
|
223
|
-
throw new errors_1.CliError('Missing org. Use org/agent or set default org.');
|
|
224
|
-
}
|
|
225
|
-
const agentMeta = await (0, api_1.getAgentWithFallback)(resolved, org, parsed.agent, parsed.version);
|
|
226
|
-
// Part 1: Pre-call balance check for paid agents
|
|
227
|
-
let pricingInfo;
|
|
228
|
-
if ((0, pricing_1.isPaidAgent)(agentMeta)) {
|
|
229
|
-
// Detect ownership: compare agent's org with caller's org
|
|
230
|
-
let isOwner = false;
|
|
231
|
-
try {
|
|
232
|
-
const callerOrg = await (0, api_1.getOrg)(resolved);
|
|
233
|
-
// Use org_id when available (preferred), org_slug as fallback
|
|
234
|
-
const agentOrgId = agentMeta.org_id;
|
|
235
|
-
const agentOrgSlug = agentMeta.org_slug;
|
|
236
|
-
if (agentOrgId && callerOrg.id === agentOrgId) {
|
|
237
|
-
isOwner = true;
|
|
238
|
-
}
|
|
239
|
-
else if (agentOrgSlug && callerOrg.slug === agentOrgSlug) {
|
|
240
|
-
isOwner = true;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
// If we can't determine ownership, treat as non-owner (fail-safe)
|
|
245
|
-
isOwner = false;
|
|
246
|
-
}
|
|
247
|
-
if (isOwner) {
|
|
248
|
-
// Owner: show free message, no balance check needed
|
|
249
|
-
if (!options.json)
|
|
250
|
-
process.stderr.write(`Cost: FREE (author)\n\n`);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
// Non-owner: check balance
|
|
254
|
-
const price = agentMeta.price_per_call_cents;
|
|
255
|
-
pricingInfo = { price_cents: price ?? null };
|
|
256
|
-
if (!price || price <= 0) {
|
|
257
|
-
// Price missing or invalid - warn but proceed (server will enforce)
|
|
258
|
-
if (!options.json)
|
|
259
|
-
process.stderr.write(`Warning: Pricing data unavailable. The server will verify payment.\n\n`);
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
// Valid price - check balance
|
|
263
|
-
try {
|
|
264
|
-
const balanceData = await (0, api_1.getCreditsBalance)(resolved);
|
|
265
|
-
const balance = balanceData.balance_cents;
|
|
266
|
-
if (balance < price) {
|
|
267
|
-
// Insufficient balance
|
|
268
|
-
process.stderr.write(`Insufficient credits:\n` +
|
|
269
|
-
` Balance: $${(balance / 100).toFixed(2)}\n` +
|
|
270
|
-
` Required: $${(price / 100).toFixed(2)}\n\n` +
|
|
271
|
-
`Add credits:\n` +
|
|
272
|
-
` orch billing add 5\n` +
|
|
273
|
-
` orch billing balance # check current balance\n`);
|
|
274
|
-
process.exit(errors_1.ExitCodes.PERMISSION_DENIED);
|
|
275
|
-
}
|
|
276
|
-
// Sufficient balance - show cost preview
|
|
277
|
-
if (!options.json)
|
|
278
|
-
process.stderr.write(`Cost: $${(price / 100).toFixed(2)}/call\n\n`);
|
|
279
|
-
}
|
|
280
|
-
catch (err) {
|
|
281
|
-
// Balance check failed - warn but proceed (server will enforce)
|
|
282
|
-
if (!options.json)
|
|
283
|
-
process.stderr.write(`Warning: Could not verify balance. The server will check payment.\n\n`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
const endpoint = options.endpoint?.trim() || agentMeta.default_endpoint || 'analyze';
|
|
289
|
-
const headers = {
|
|
290
|
-
Authorization: `Bearer ${resolved.apiKey}`,
|
|
291
|
-
};
|
|
292
|
-
if (options.tenant) {
|
|
293
|
-
headers['X-OrchAgent-Tenant'] = options.tenant;
|
|
294
|
-
}
|
|
295
|
-
const supportedProviders = agentMeta.supported_providers || ['any'];
|
|
296
|
-
let llmKey;
|
|
297
|
-
let llmProvider;
|
|
298
|
-
// Resolve effective provider: CLI flag > config default
|
|
299
|
-
const configDefaultProvider = await (0, config_1.getDefaultProvider)();
|
|
300
|
-
const effectiveProvider = options.provider ?? configDefaultProvider;
|
|
301
|
-
if (options.key) {
|
|
302
|
-
// Explicit key provided - require provider
|
|
303
|
-
if (!effectiveProvider) {
|
|
304
|
-
throw new errors_1.CliError('When using --key, you must also specify --provider (openai, anthropic, or gemini)');
|
|
305
|
-
}
|
|
306
|
-
(0, llm_1.validateProvider)(effectiveProvider);
|
|
307
|
-
// Warn on potential model/provider mismatch
|
|
308
|
-
if (options.model && effectiveProvider) {
|
|
309
|
-
const modelLower = options.model.toLowerCase();
|
|
310
|
-
const providerPatterns = {
|
|
311
|
-
openai: /^(gpt-|o1-|o3-|davinci|text-)/,
|
|
312
|
-
anthropic: /^claude-/,
|
|
313
|
-
gemini: /^gemini-/,
|
|
314
|
-
ollama: /^(llama|mistral|deepseek|phi|qwen)/,
|
|
315
|
-
};
|
|
316
|
-
const expectedPattern = providerPatterns[effectiveProvider];
|
|
317
|
-
if (expectedPattern && !expectedPattern.test(modelLower)) {
|
|
318
|
-
process.stderr.write(`Warning: Model '${options.model}' may not be a ${effectiveProvider} model.\n\n`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
llmKey = options.key;
|
|
322
|
-
llmProvider = effectiveProvider;
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
// Try to detect from environment or server
|
|
326
|
-
// If provider specified (flag or config default), prioritize that provider
|
|
327
|
-
let providersToCheck = supportedProviders;
|
|
328
|
-
if (effectiveProvider) {
|
|
329
|
-
(0, llm_1.validateProvider)(effectiveProvider);
|
|
330
|
-
providersToCheck = [effectiveProvider];
|
|
331
|
-
// Warn on potential model/provider mismatch
|
|
332
|
-
if (options.model) {
|
|
333
|
-
const modelLower = options.model.toLowerCase();
|
|
334
|
-
const providerPatterns = {
|
|
335
|
-
openai: /^(gpt-|o1-|o3-|davinci|text-)/,
|
|
336
|
-
anthropic: /^claude-/,
|
|
337
|
-
gemini: /^gemini-/,
|
|
338
|
-
ollama: /^(llama|mistral|deepseek|phi|qwen)/,
|
|
339
|
-
};
|
|
340
|
-
const expectedPattern = providerPatterns[effectiveProvider];
|
|
341
|
-
if (expectedPattern && !expectedPattern.test(modelLower)) {
|
|
342
|
-
process.stderr.write(`Warning: Model '${options.model}' may not be a ${effectiveProvider} model.\n\n`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
const detected = await (0, llm_1.detectLlmKey)(providersToCheck, resolved);
|
|
347
|
-
if (detected) {
|
|
348
|
-
llmKey = detected.key;
|
|
349
|
-
llmProvider = detected.provider;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
// LLM credentials will be added to request body (not headers) for security
|
|
353
|
-
// Headers can be logged by proxies/load balancers, body is not logged by default
|
|
354
|
-
let llmCredentials;
|
|
355
|
-
if (llmKey && llmProvider) {
|
|
356
|
-
llmCredentials = {
|
|
357
|
-
api_key: llmKey,
|
|
358
|
-
provider: llmProvider,
|
|
359
|
-
...(options.model && { model: options.model }),
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
else if (agentMeta.type === 'prompt') {
|
|
363
|
-
// Warn if no key found for prompt-based agent
|
|
364
|
-
const searchedProviders = effectiveProvider ? [effectiveProvider] : supportedProviders;
|
|
365
|
-
const providerList = searchedProviders.join(', ');
|
|
366
|
-
process.stderr.write(`Warning: No LLM key found for provider(s): ${providerList}\n` +
|
|
367
|
-
`Set an env var (e.g., OPENAI_API_KEY), run 'orchagent keys add <provider>', use --key, or configure in web dashboard\n\n`);
|
|
368
|
-
}
|
|
369
|
-
// Add skill headers
|
|
370
|
-
if (options.skills) {
|
|
371
|
-
headers['X-OrchAgent-Skills'] = options.skills;
|
|
372
|
-
}
|
|
373
|
-
if (options.skillsOnly) {
|
|
374
|
-
headers['X-OrchAgent-Skills-Only'] = options.skillsOnly;
|
|
375
|
-
}
|
|
376
|
-
if (options.noSkills) {
|
|
377
|
-
headers['X-OrchAgent-No-Skills'] = 'true';
|
|
378
|
-
}
|
|
379
|
-
let body;
|
|
380
|
-
let sourceLabel;
|
|
381
|
-
const filePaths = [
|
|
382
|
-
...(options.file ?? []),
|
|
383
|
-
...(file ? [file] : []),
|
|
384
|
-
];
|
|
385
|
-
if (options.data) {
|
|
386
|
-
if (filePaths.length > 0 || options.metadata) {
|
|
387
|
-
throw new errors_1.CliError('Cannot use --data with file uploads or --metadata.');
|
|
388
|
-
}
|
|
389
|
-
// Parse JSON and inject llm_credentials if available
|
|
390
|
-
const resolvedBody = await resolveJsonBody(options.data);
|
|
391
|
-
// Warn if payload contains local path references
|
|
392
|
-
warnIfLocalPathReference(resolvedBody);
|
|
393
|
-
if (llmCredentials) {
|
|
394
|
-
const bodyObj = JSON.parse(resolvedBody);
|
|
395
|
-
bodyObj.llm_credentials = llmCredentials;
|
|
396
|
-
body = JSON.stringify(bodyObj);
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
body = resolvedBody;
|
|
400
|
-
}
|
|
401
|
-
headers['Content-Type'] = 'application/json';
|
|
402
|
-
}
|
|
403
|
-
else if ((filePaths.length > 0 || options.metadata) && agentMeta.type === 'prompt') {
|
|
404
|
-
// Prompt agent + files/metadata: read content and send as JSON
|
|
405
|
-
const fieldName = options.fileField || inferFileField(agentMeta.input_schema);
|
|
406
|
-
let bodyObj = {};
|
|
407
|
-
// Include metadata if provided
|
|
408
|
-
if (options.metadata) {
|
|
409
|
-
try {
|
|
410
|
-
bodyObj = JSON.parse(options.metadata);
|
|
411
|
-
}
|
|
412
|
-
catch {
|
|
413
|
-
throw new errors_1.CliError('--metadata must be valid JSON.');
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (filePaths.length === 1) {
|
|
417
|
-
// Single file: map content to the inferred/specified schema field
|
|
418
|
-
const fileContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
419
|
-
bodyObj[fieldName] = fileContent;
|
|
420
|
-
sourceLabel = filePaths[0];
|
|
421
|
-
}
|
|
422
|
-
else if (filePaths.length > 1) {
|
|
423
|
-
// Multiple files: map first to the schema field, add all as files object
|
|
424
|
-
const allContents = {};
|
|
425
|
-
for (const fp of filePaths) {
|
|
426
|
-
allContents[path_1.default.basename(fp)] = await promises_1.default.readFile(fp, 'utf-8');
|
|
427
|
-
}
|
|
428
|
-
// Set the primary field to the first file's content
|
|
429
|
-
const firstContent = await promises_1.default.readFile(filePaths[0], 'utf-8');
|
|
430
|
-
bodyObj[fieldName] = firstContent;
|
|
431
|
-
bodyObj.files = allContents;
|
|
432
|
-
sourceLabel = `${filePaths.length} files`;
|
|
433
|
-
}
|
|
434
|
-
if (llmCredentials) {
|
|
435
|
-
bodyObj.llm_credentials = llmCredentials;
|
|
436
|
-
}
|
|
437
|
-
body = JSON.stringify(bodyObj);
|
|
438
|
-
headers['Content-Type'] = 'application/json';
|
|
439
|
-
}
|
|
440
|
-
else if (filePaths.length > 0 || options.metadata) {
|
|
441
|
-
// Tool: handle multipart file uploads
|
|
442
|
-
// Inject llm_credentials into metadata if available
|
|
443
|
-
let metadata = options.metadata;
|
|
444
|
-
if (llmCredentials) {
|
|
445
|
-
const metaObj = metadata ? JSON.parse(metadata) : {};
|
|
446
|
-
metaObj.llm_credentials = llmCredentials;
|
|
447
|
-
metadata = JSON.stringify(metaObj);
|
|
448
|
-
}
|
|
449
|
-
const multipart = await buildMultipartBody(filePaths, metadata);
|
|
450
|
-
body = multipart.body;
|
|
451
|
-
sourceLabel = multipart.sourceLabel;
|
|
452
|
-
}
|
|
453
|
-
else if (llmCredentials) {
|
|
454
|
-
// No data or files, but we have LLM credentials - send as JSON body
|
|
455
|
-
body = JSON.stringify({ llm_credentials: llmCredentials });
|
|
456
|
-
headers['Content-Type'] = 'application/json';
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
// No data, files, or credentials - check for stdin
|
|
460
|
-
const multipart = await buildMultipartBody(undefined, options.metadata);
|
|
461
|
-
body = multipart.body;
|
|
462
|
-
sourceLabel = multipart.sourceLabel;
|
|
463
|
-
}
|
|
464
|
-
const url = `${resolved.apiUrl.replace(/\/$/, '')}/${org}/${parsed.agent}/${parsed.version}/${endpoint}`;
|
|
465
|
-
// Make the API call with a spinner (suppress in --json mode for clean machine-readable output)
|
|
466
|
-
const spinner = options.json ? null : (0, spinner_1.createSpinner)(`Calling ${org}/${parsed.agent}@${parsed.version}...`);
|
|
467
|
-
spinner?.start();
|
|
468
|
-
let response;
|
|
469
|
-
try {
|
|
470
|
-
response = await (0, api_1.safeFetchWithRetryForCalls)(url, {
|
|
471
|
-
method: 'POST',
|
|
472
|
-
headers,
|
|
473
|
-
body,
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
catch (err) {
|
|
477
|
-
spinner?.fail(`Call failed: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
478
|
-
throw err;
|
|
479
|
-
}
|
|
480
|
-
if (!response.ok) {
|
|
481
|
-
const text = await response.text();
|
|
482
|
-
let payload;
|
|
483
|
-
try {
|
|
484
|
-
payload = JSON.parse(text);
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
payload = text;
|
|
488
|
-
}
|
|
489
|
-
// Handle specific error codes with helpful messages
|
|
490
|
-
const errorCode = typeof payload === 'object' && payload
|
|
491
|
-
? payload.error?.code
|
|
492
|
-
: undefined;
|
|
493
|
-
// Part 2: Handle 402 Payment Required
|
|
494
|
-
if (response.status === 402 || errorCode === 'INSUFFICIENT_CREDITS') {
|
|
495
|
-
spinner?.fail('Insufficient credits');
|
|
496
|
-
let errorMessage = 'Insufficient credits to call this agent.\n\n';
|
|
497
|
-
// Use pricing info from pre-call check if available
|
|
498
|
-
if (pricingInfo?.price_cents) {
|
|
499
|
-
errorMessage += `This agent costs $${(pricingInfo.price_cents / 100).toFixed(2)} per call.\n\n`;
|
|
500
|
-
}
|
|
501
|
-
errorMessage +=
|
|
502
|
-
'Add credits:\n' +
|
|
503
|
-
' orch billing add 5\n' +
|
|
504
|
-
' orch billing balance # check current balance\n';
|
|
505
|
-
throw new errors_1.CliError(errorMessage, errors_1.ExitCodes.PERMISSION_DENIED);
|
|
506
|
-
}
|
|
507
|
-
if (errorCode === 'LLM_KEY_REQUIRED') {
|
|
508
|
-
spinner?.fail('LLM key required');
|
|
509
|
-
throw new errors_1.CliError('This public agent requires you to provide an LLM key.\n' +
|
|
510
|
-
'Use --key <key> --provider <provider> or set OPENAI_API_KEY/ANTHROPIC_API_KEY env var.');
|
|
511
|
-
}
|
|
512
|
-
if (errorCode === 'LLM_RATE_LIMITED') {
|
|
513
|
-
const rateLimitMsg = typeof payload === 'object' && payload
|
|
514
|
-
? payload.error?.message || 'Rate limit exceeded'
|
|
515
|
-
: 'Rate limit exceeded';
|
|
516
|
-
spinner?.fail('Rate limited by LLM provider');
|
|
517
|
-
throw new errors_1.CliError(rateLimitMsg + '\n\n' +
|
|
518
|
-
'This is the LLM provider\'s rate limit on your API key, not an OrchAgent limit.\n' +
|
|
519
|
-
'To switch providers: orch call <agent> --provider <gemini|anthropic|openai>', errors_1.ExitCodes.RATE_LIMITED);
|
|
520
|
-
}
|
|
521
|
-
const message = typeof payload === 'object' && payload
|
|
522
|
-
? payload.error
|
|
523
|
-
?.message ||
|
|
524
|
-
payload.message ||
|
|
525
|
-
response.statusText
|
|
526
|
-
: response.statusText;
|
|
527
|
-
spinner?.fail(`Call failed: ${message}`);
|
|
528
|
-
throw new errors_1.CliError(message);
|
|
529
|
-
}
|
|
530
|
-
spinner?.succeed(`Called ${org}/${parsed.agent}@${parsed.version}`);
|
|
531
|
-
// After successful call, if it was a paid agent, show cost (suppress in --json mode)
|
|
532
|
-
if (!options.json && (0, pricing_1.isPaidAgent)(agentMeta) && pricingInfo?.price_cents && pricingInfo.price_cents > 0) {
|
|
533
|
-
process.stderr.write(`\nCost: $${(pricingInfo.price_cents / 100).toFixed(2)} USD\n`);
|
|
534
|
-
}
|
|
535
|
-
// Track successful call
|
|
536
|
-
const inputType = filePaths.length > 0
|
|
537
|
-
? 'file'
|
|
538
|
-
: options.data
|
|
539
|
-
? 'json'
|
|
540
|
-
: sourceLabel === 'stdin'
|
|
541
|
-
? 'stdin'
|
|
542
|
-
: sourceLabel === 'metadata'
|
|
543
|
-
? 'metadata'
|
|
544
|
-
: 'empty';
|
|
545
|
-
await (0, analytics_1.track)('cli_call', {
|
|
546
|
-
agent: `${org}/${parsed.agent}@${parsed.version}`,
|
|
547
|
-
input_type: inputType,
|
|
548
|
-
});
|
|
549
|
-
if (options.output) {
|
|
550
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
551
|
-
await promises_1.default.writeFile(options.output, buffer);
|
|
552
|
-
process.stdout.write(`Saved response to ${options.output}\n`);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
const text = await response.text();
|
|
556
|
-
let payload;
|
|
557
|
-
try {
|
|
558
|
-
payload = JSON.parse(text);
|
|
559
|
-
}
|
|
560
|
-
catch {
|
|
561
|
-
payload = text;
|
|
562
|
-
}
|
|
563
|
-
if (options.json) {
|
|
564
|
-
if (typeof payload === 'string') {
|
|
565
|
-
process.stdout.write(`${payload}\n`);
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
(0, output_1.printJson)(payload);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
if (typeof payload === 'string') {
|
|
572
|
-
process.stdout.write(`${payload}\n`);
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
(0, output_1.printJson)(payload);
|
|
11
|
+
.command('call')
|
|
12
|
+
.description('Deprecated: use "orch run" instead')
|
|
13
|
+
.allowUnknownOption()
|
|
14
|
+
.allowExcessArguments()
|
|
15
|
+
.action(() => {
|
|
16
|
+
process.stderr.write(`The 'call' command has been merged into 'run'.\n\n` +
|
|
17
|
+
`Cloud execution is now the default behavior of 'orch run':\n` +
|
|
18
|
+
` orch run <agent> --data '{...}' # runs on server (cloud)\n` +
|
|
19
|
+
` orch run <agent> --local --data '...' # runs locally\n\n` +
|
|
20
|
+
`Replace 'orch call' with 'orch run' in your commands.\n`);
|
|
21
|
+
process.exit(1);
|
|
576
22
|
});
|
|
577
23
|
}
|
package/dist/commands/info.js
CHANGED
|
@@ -173,7 +173,7 @@ function registerInfoCommand(program) {
|
|
|
173
173
|
process.stdout.write(`Price: ${color(priceStr)}\n`);
|
|
174
174
|
// If paid, show server-only message for non-owners
|
|
175
175
|
if ((0, pricing_1.isPaidAgent)(agentData)) {
|
|
176
|
-
process.stdout.write(chalk_1.default.gray('Note: Paid agents run on server only (use orch
|
|
176
|
+
process.stdout.write(chalk_1.default.gray('Note: Paid agents run on server only (use orch run)\n'));
|
|
177
177
|
process.stdout.write(chalk_1.default.gray(' Owners can still download for development/testing\n'));
|
|
178
178
|
}
|
|
179
179
|
if (agentData.type === 'tool') {
|