Skip to content

Commit 5600648

Browse files
authored
feat; improve codemode (#147)
1 parent b6e702e commit 5600648

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

src/plugins/codemode/codemode.go

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"regexp"
1111
"sort"
1212
"strings"
13+
"time"
1314

1415
"github.com/traefik/yaegi/interp"
1516
"github.com/traefik/yaegi/stdlib"
@@ -250,6 +251,15 @@ func (c *CodeModeUTCP) Execute(ctx context.Context, args CodeModeArgs) (CodeMode
250251
return c.executeFunc(ctx, args)
251252
}
252253

254+
// 1. Enforce Timeout via Context
255+
// Convert integer ms to Duration. Default to 30s if invalid.
256+
timeoutMs := args.Timeout
257+
if timeoutMs <= 0 {
258+
timeoutMs = 30000
259+
}
260+
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutMs)*time.Millisecond)
261+
defer cancel()
262+
253263
i, stdout, stderr := newInterpreter()
254264

255265
if err := injectHelpers(i, c.client); err != nil {
@@ -261,20 +271,69 @@ func (c *CodeModeUTCP) Execute(ctx context.Context, args CodeModeArgs) (CodeMode
261271
return CodeModeResult{}, fmt.Errorf("failed to prepare program: %w", err)
262272
}
263273

264-
if _, err := i.Eval(wrapped); err != nil {
265-
return CodeModeResult{}, fmt.Errorf("code execution failed: %w\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
274+
// 2. Structure for async result handling
275+
type evalResult struct {
276+
val reflect.Value
277+
err error
266278
}
279+
done := make(chan evalResult, 1)
280+
281+
// 3. Run Eval in a Goroutine
282+
go func() {
283+
// Safety: recover from internal interpreter panics
284+
defer func() {
285+
if r := recover(); r != nil {
286+
done <- evalResult{err: fmt.Errorf("interpreter panic: %v", r)}
287+
}
288+
}()
289+
290+
// Phase A: Compilation & Definition
291+
if _, err := i.Eval(wrapped); err != nil {
292+
done <- evalResult{err: fmt.Errorf("compilation failed: %w", err)}
293+
return
294+
}
267295

268-
v, err := i.Eval(`main.run()`)
269-
if err != nil {
270-
return CodeModeResult{}, fmt.Errorf("failed to get return value: %w\n%s", err, stderr.String())
271-
}
296+
// Phase B: Execution of the wrapped runner
297+
v, err := i.Eval(`main.run()`)
298+
done <- evalResult{val: v, err: err}
299+
}()
300+
301+
// 4. Wait for Completion or Timeout
302+
select {
303+
case <-ctx.Done():
304+
return CodeModeResult{
305+
Stdout: stdout.String(),
306+
Stderr: stderr.String(),
307+
}, fmt.Errorf("execution timed out after %dms", timeoutMs)
308+
309+
case res := <-done:
310+
if res.err != nil {
311+
return CodeModeResult{
312+
Stdout: stdout.String(),
313+
Stderr: stderr.String(),
314+
}, fmt.Errorf("runtime error: %w\nstdout: %s\nstderr: %s", res.err, stdout.String(), stderr.String())
315+
}
272316

273-
return CodeModeResult{
274-
Value: v.Interface(),
275-
Stdout: stdout.String(),
276-
Stderr: stderr.String(),
277-
}, nil
317+
// 5. Handle Result & Check for Error Objects
318+
finalVal := res.val.Interface()
319+
finalStderr := stderr.String()
320+
321+
// FIX: If the user code returned an `error` type (e.g. "return err"),
322+
// we capture it and move it to Stderr so the Tool Handler treats it as a failure.
323+
if errObj, ok := finalVal.(error); ok {
324+
if finalStderr != "" {
325+
finalStderr += "\n"
326+
}
327+
finalStderr += "Script returned error: " + errObj.Error()
328+
finalVal = nil // Nullify value since it was an error
329+
}
330+
331+
return CodeModeResult{
332+
Value: finalVal,
333+
Stdout: stdout.String(),
334+
Stderr: finalStderr,
335+
}, nil
336+
}
278337
}
279338

280339
func (s *codeModeStream) Next() (any, error) {

src/plugins/codemode/orchestrator.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ SNIPPET RULES
9797
- codemode.Sprintf(format, ...), codemode.Errorf(format, ...)
9898
- No imports, no package — ONLY Go statements.
9999
- Don't Declare 'var __out'
100+
- Always assign to '__out' using '=' (e.g., '__out = ...').
101+
- If you need to assign a new variable along with __out, declare the error first:
102+
var err error
103+
__out, err = codemode.CallTool(...)
100104
- The final result MUST be assigned to '__out', containing all intermediate and final results.
101105
- If ANY streaming tool is used, set "stream": true.
102106

0 commit comments

Comments
 (0)