@kruntime/komputer 0.1.1 → 0.1.3

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.
@@ -2646,6 +2646,9 @@ export class AgentSession {
2646
2646
  throw new Error(`command not found: ${name}`);
2647
2647
  if (command.agent && command.agent !== this.agentName)
2648
2648
  throw new Error(`command ${name} belongs to agent ${command.agent}`);
2649
+ if (isCommandHelpInput(input)) {
2650
+ return { type: 'stdout', text: `${commandHelpText(name, command)}\n`, code: 0 };
2651
+ }
2649
2652
  const by = normalizeBy(options.by || (typeof input.by === 'string' ? input.by : undefined));
2650
2653
  const toolName = `command:${name}`;
2651
2654
  const before = await this.runHook('tool.before', { tool: toolName, command: name, input, by });
@@ -2662,7 +2665,16 @@ export class AgentSession {
2662
2665
  throw new Error(authority.reason);
2663
2666
  }
2664
2667
  const toolUseId = id('tool');
2665
- const ctx = this.runtime.runtimeContext(this, { kind: 'command', name, input: currentInput, args: currentInput });
2668
+ const parsed = parseCommandInput(name, command, currentInput);
2669
+ const ctx = this.runtime.runtimeContext(this, {
2670
+ kind: 'command',
2671
+ name,
2672
+ input: currentInput,
2673
+ args: parsed.args,
2674
+ argv: parsed.argv,
2675
+ stdin: parsed.stdin,
2676
+ env: parsed.env,
2677
+ });
2666
2678
  await this.emit({ type: 'tool', status: 'requested', tool: toolName, input: currentInput, opId: toolUseId });
2667
2679
  await this.trace('tool.started', { name, input: currentInput, by, toolUseId });
2668
2680
  let result;
@@ -2677,20 +2689,30 @@ export class AgentSession {
2677
2689
  result = invocationMode ? await this.withTemporaryMode(invocationMode, runShell) : await runShell();
2678
2690
  }
2679
2691
  else if (command.kind === 'markdown') {
2680
- const args = Object.keys(currentInput).length ? `\n\nInput:\n${JSON.stringify(currentInput, null, 2)}` : '';
2692
+ const prompt = String(command.prompt ?? '');
2693
+ const rendered = renderMarkdownCommandPrompt(prompt, parsed.args, parsed.stdin);
2694
+ const args = hasCommandTemplate(prompt) || Object.keys(currentInput).length === 0
2695
+ ? ''
2696
+ : `\n\nInput:\n${JSON.stringify(currentInput, null, 2)}`;
2681
2697
  const invocationMode = command.readonly === true
2682
2698
  ? 'readonly'
2683
2699
  : typeof command.mode === 'string'
2684
2700
  ? command.mode
2685
2701
  : undefined;
2686
- const runMarkdown = () => this.run(`${String(command.prompt ?? '')}${args}`, { by: options.by || `command:${name}`, model: typeof command.model === 'string' ? command.model : undefined });
2702
+ const runMarkdown = () => this.run(`${rendered}${args}`, { by: options.by || `command:${name}`, model: typeof command.model === 'string' ? command.model : undefined });
2687
2703
  result = invocationMode ? await this.withTemporaryMode(invocationMode, runMarkdown) : await runMarkdown();
2688
2704
  }
2689
2705
  else {
2690
- if (!command.handler)
2691
- throw new Error(`command has no handler: ${name}`);
2692
- const handler = command.handler;
2693
- result = await this.withFsActor(options.by ? by : `command:${name}`, () => Promise.resolve(handler(ctx, currentInput)));
2706
+ const main = command.main ?? command.handler;
2707
+ if (!main)
2708
+ throw new Error(`command has no main: ${name}`);
2709
+ const runModule = () => this.withFsActor(options.by ? by : `command:${name}`, () => Promise.resolve(main(ctx, parsed.args)));
2710
+ const invocationMode = command.readonly === true
2711
+ ? 'readonly'
2712
+ : typeof command.mode === 'string'
2713
+ ? command.mode
2714
+ : undefined;
2715
+ result = invocationMode ? await this.withTemporaryMode(invocationMode, runModule) : await runModule();
2694
2716
  }
2695
2717
  const after = await this.runHook('tool.after', { tool: toolName, command: name, input: currentInput, result, by });
2696
2718
  if (after.rejected) {
@@ -2992,6 +3014,169 @@ function commandErrorText(command, error) {
2992
3014
  function isHelpArg(value) {
2993
3015
  return value === '--help' || value === '-h';
2994
3016
  }
3017
+ function isCommandHelpInput(input) {
3018
+ const argv = stringArrayField(input, 'argv') || [];
3019
+ return argv.length === 1 && isHelpArg(argv[0]);
3020
+ }
3021
+ function parseCommandInput(name, command, input) {
3022
+ const argv = stringArrayField(input, 'argv') || [];
3023
+ const stdin = typeof input.stdin === 'string' ? input.stdin : '';
3024
+ const envRecord = recordField(input, 'env');
3025
+ const env = envRecord ? stringRecord(envRecord) : undefined;
3026
+ if (command.args === 'none') {
3027
+ const extra = argv.filter((arg) => arg !== '--');
3028
+ if (extra.length)
3029
+ throw new Error(`${name}: unexpected argument: ${extra[0]}`);
3030
+ return { argv, stdin, env, args: {} };
3031
+ }
3032
+ if (!command.args || command.args === 'argv')
3033
+ return { argv, stdin, env, args: input };
3034
+ return { argv, stdin, env, args: parseStructuredCommandArgs(name, command.args, input, argv) };
3035
+ }
3036
+ function parseStructuredCommandArgs(name, specs, input, argv) {
3037
+ const parsed = {};
3038
+ const options = commandOptionSpecs(specs);
3039
+ for (const [key, spec] of Object.entries(specs)) {
3040
+ if ('defaultValue' in spec)
3041
+ parsed[key] = spec.defaultValue;
3042
+ else if ('default' in spec && typeof spec.default !== 'function')
3043
+ parsed[key] = spec.default;
3044
+ }
3045
+ for (const [key, value] of Object.entries(input)) {
3046
+ if (key !== 'argv' && key !== 'stdin' && key !== 'env' && specs[key])
3047
+ parsed[key] = value;
3048
+ }
3049
+ const positional = Object.entries(specs)
3050
+ .filter(([, spec]) => typeof spec.position === 'number')
3051
+ .sort((a, b) => Number(a[1].position) - Number(b[1].position));
3052
+ let positionalIndex = 0;
3053
+ for (let index = 0; index < argv.length; index += 1) {
3054
+ const token = argv[index];
3055
+ if (token === '--') {
3056
+ for (const rest of argv.slice(index + 1))
3057
+ assignPositional(name, parsed, positional, positionalIndex++, rest);
3058
+ break;
3059
+ }
3060
+ if (token.startsWith('--')) {
3061
+ const body = token.slice(2);
3062
+ const eq = body.indexOf('=');
3063
+ const option = eq >= 0 ? body.slice(0, eq) : body;
3064
+ const resolved = options.get(option);
3065
+ if (!resolved)
3066
+ throw new Error(`${name}: unsupported option: --${option}`);
3067
+ const [key, spec] = resolved;
3068
+ if (spec.type === 'boolean' && eq < 0) {
3069
+ parsed[key] = true;
3070
+ continue;
3071
+ }
3072
+ const value = eq >= 0 ? body.slice(eq + 1) : argv[++index];
3073
+ if (value === undefined)
3074
+ throw new Error(`${name}: option requires an argument: --${key}`);
3075
+ assignCommandArg(name, parsed, key, spec, value);
3076
+ continue;
3077
+ }
3078
+ const eq = token.indexOf('=');
3079
+ if (eq > 0 && specs[token.slice(0, eq)]) {
3080
+ const key = token.slice(0, eq);
3081
+ assignCommandArg(name, parsed, key, specs[key], token.slice(eq + 1));
3082
+ continue;
3083
+ }
3084
+ assignPositional(name, parsed, positional, positionalIndex++, token);
3085
+ }
3086
+ for (const [key, spec] of Object.entries(specs)) {
3087
+ if (spec.required === true && (parsed[key] === undefined || parsed[key] === '')) {
3088
+ throw new Error(`${name}: missing required argument: --${key}`);
3089
+ }
3090
+ if (parsed[key] !== undefined)
3091
+ parsed[key] = coerceCommandArg(name, key, spec, parsed[key]);
3092
+ }
3093
+ return parsed;
3094
+ }
3095
+ function commandOptionSpecs(specs) {
3096
+ const options = new Map();
3097
+ for (const [key, spec] of Object.entries(specs)) {
3098
+ options.set(key, [key, spec]);
3099
+ const flag = typeof spec.flag === 'string' ? normalizeCommandFlag(spec.flag) : undefined;
3100
+ if (flag)
3101
+ options.set(flag, [key, spec]);
3102
+ }
3103
+ return options;
3104
+ }
3105
+ function normalizeCommandFlag(flag) {
3106
+ return flag.trim().replace(/^-+/, '');
3107
+ }
3108
+ function assignPositional(name, parsed, positional, index, value) {
3109
+ const entry = positional[index];
3110
+ if (!entry)
3111
+ throw new Error(`${name}: unexpected argument: ${String(value)}`);
3112
+ assignCommandArg(name, parsed, entry[0], entry[1], value);
3113
+ }
3114
+ function assignCommandArg(name, parsed, key, spec, value) {
3115
+ const coerced = coerceCommandArg(name, key, spec, value);
3116
+ if (spec.repeat) {
3117
+ const current = Array.isArray(parsed[key]) ? parsed[key] : parsed[key] === undefined ? [] : [parsed[key]];
3118
+ current.push(coerced);
3119
+ parsed[key] = current;
3120
+ return;
3121
+ }
3122
+ parsed[key] = coerced;
3123
+ }
3124
+ function coerceCommandArg(name, key, spec, value) {
3125
+ if (value === undefined)
3126
+ return value;
3127
+ if (spec.type === 'string' || spec.type === 'path')
3128
+ return String(value);
3129
+ if (spec.type === 'number') {
3130
+ const number = typeof value === 'number' ? value : Number(String(value));
3131
+ if (!Number.isFinite(number))
3132
+ throw new Error(`${name}: invalid value for --${key}: expected number`);
3133
+ return number;
3134
+ }
3135
+ if (spec.type === 'boolean') {
3136
+ if (typeof value === 'boolean')
3137
+ return value;
3138
+ const text = String(value).toLowerCase();
3139
+ if (text === 'true' || text === '1' || text === 'yes')
3140
+ return true;
3141
+ if (text === 'false' || text === '0' || text === 'no')
3142
+ return false;
3143
+ throw new Error(`${name}: invalid value for --${key}: expected boolean`);
3144
+ }
3145
+ if (spec.type === 'json') {
3146
+ if (typeof value !== 'string')
3147
+ return value;
3148
+ try {
3149
+ return JSON.parse(value);
3150
+ }
3151
+ catch {
3152
+ throw new Error(`${name}: invalid value for --${key}: expected json`);
3153
+ }
3154
+ }
3155
+ return value;
3156
+ }
3157
+ function stringRecord(record) {
3158
+ const output = {};
3159
+ for (const [key, value] of Object.entries(record)) {
3160
+ if (typeof value === 'string')
3161
+ output[key] = value;
3162
+ }
3163
+ return output;
3164
+ }
3165
+ function hasCommandTemplate(prompt) {
3166
+ return /\{\{\s*(?:arg|args)\.[A-Za-z0-9_.-]+\s*\}\}|\{\{\s*stdin\s*\}\}/.test(prompt);
3167
+ }
3168
+ function renderMarkdownCommandPrompt(prompt, args, stdin) {
3169
+ return prompt.replace(/\{\{\s*((?:arg|args)\.([A-Za-z0-9_.-]+)|stdin)\s*\}\}/g, (_match, token, argName) => {
3170
+ if (token === 'stdin')
3171
+ return stdin;
3172
+ const value = argName ? args[argName] : undefined;
3173
+ if (value === undefined || value === null)
3174
+ return '';
3175
+ if (typeof value === 'string')
3176
+ return value;
3177
+ return JSON.stringify(value);
3178
+ });
3179
+ }
2995
3180
  function isExpiredWorker(worker) {
2996
3181
  const expiresAt = typeof worker.expiresAt === 'string' ? Date.parse(worker.expiresAt) : Number.NaN;
2997
3182
  return Number.isFinite(expiresAt) && expiresAt <= Date.now();