Add session and reasoning controls

This commit is contained in:
2026-08-29 14:22:45 +00:00
parent 1a0f15e210
commit 06b37c2bae
8 changed files with 355 additions and 28 deletions

View File

@@ -103,12 +103,14 @@ func TestParseRunArgsFlagMapping(t *testing.T) {
"--prompt", "prompt.a",
"--prompt-version", "2",
"--profile", "profile.a",
"--session-id", "session-1",
"--input", "a=b",
"--llm-base-url", "http://x/v1",
"--model", "m",
"--temperature", "0.7",
"--max-tokens", "111",
"--top-p", "0.8",
"--reasoning-effort", "medium",
"--timeout", "30s",
"--api-key-env", "SCRIPTORIUM_API_KEY",
})
@@ -121,6 +123,9 @@ func TestParseRunArgsFlagMapping(t *testing.T) {
if cfg.promptID != "prompt.a" || cfg.promptVersion != "2" || cfg.profileID != "profile.a" {
t.Fatalf("unexpected prompt/version/profile ids: %q %q %q", cfg.promptID, cfg.promptVersion, cfg.profileID)
}
if cfg.sessionID != "session-1" || cfg.reasoningEffort != "medium" || !cfg.reasoningEffortSet {
t.Fatalf("unexpected session or reasoning configuration: %+v", cfg)
}
if !cfg.llmBaseURLSet || !cfg.modelSet || !cfg.temperatureSet || !cfg.maxTokensSet || !cfg.topPSet || !cfg.timeoutSet || !cfg.apiKeyEnvSet {
t.Fatalf("expected override flags set, got %+v", cfg)
}
@@ -206,6 +211,8 @@ func TestUsageIncludesExecutionAndServeFlags(t *testing.T) {
usage := stderr.String()
for _, want := range []string{
"--prompt-version VERSION",
"--session-id ID",
"--reasoning-effort VALUE",
"--artifact-root",
"--max-request-bytes",
"--max-artifact-bytes",
@@ -694,6 +701,39 @@ func TestBuildRunRequestPreservesNumericOverridePresence(t *testing.T) {
}
}
func TestBuildRunRequestPreservesReasoningEffortPresenceAndSessionID(t *testing.T) {
omitted, err := buildRunRequestFromConfig(&runConfig{promptID: "prompt-1"})
if err != nil {
t.Fatalf("expected omitted request to build, got %v", err)
}
if omitted.Execution != nil {
t.Fatalf("expected omitted reasoning flag to leave execution nil, got %#v", omitted.Execution)
}
for _, tc := range []struct {
name string
value string
}{
{name: "replacement", value: "high"},
{name: "clear", value: ""},
} {
t.Run(tc.name, func(t *testing.T) {
req, err := buildRunRequestFromConfig(&runConfig{
promptID: "prompt-1",
sessionID: "session-1",
reasoningEffort: tc.value,
reasoningEffortSet: true,
})
if err != nil {
t.Fatalf("expected request to build, got %v", err)
}
if req.SessionID != "session-1" || req.Execution == nil || req.Execution.ReasoningEffort == nil || *req.Execution.ReasoningEffort != tc.value {
t.Fatalf("unexpected mapped request: %#v", req)
}
})
}
}
func TestBuildRunRequestAllowsOmittedInputsAndMapsPromptVersion(t *testing.T) {
req, err := buildRunRequestFromConfig(&runConfig{
promptID: "prompt-1",
@@ -1002,6 +1042,118 @@ backends:
}
}
func TestRenderCommandMapsReasoningEffortAndSessionID(t *testing.T) {
lib := newCLITestLibrary(t)
writePromptDefinition(t, lib.promptDir, "session.yaml", `id: session
version: "1"
default_profile: local
session_id: definition-session
messages:
- role: user
content: "hello"
output:
format: text
validation_mode: none
`)
if err := os.WriteFile(filepath.Join(lib.profileDir, "local.yaml"), []byte(`id: local
endpoint: http://127.0.0.1:1/v1
model: local-model
reasoning_effort: low
`), 0o644); err != nil {
t.Fatalf("write profile fixture: %v", err)
}
for _, tc := range []struct {
name string
args []string
wantReasoning string
wantSessionID string
absentReasoning bool
}{
{name: "omitted reasoning inherits profile", wantReasoning: "low", wantSessionID: "definition-session"},
{name: "nonblank reasoning replaces profile", args: []string{"--reasoning-effort", "high"}, wantReasoning: "high", wantSessionID: "definition-session"},
{name: "empty reasoning clears profile", args: []string{"--reasoning-effort="}, wantSessionID: "definition-session", absentReasoning: true},
{name: "direct session replaces definition", args: []string{"--session-id", "direct-session"}, wantReasoning: "low", wantSessionID: "direct-session"},
} {
t.Run(tc.name, func(t *testing.T) {
args := []string{"--prompt-dir", lib.promptDir, "--profile-dir", lib.profileDir, "--prompt", "session"}
args = append(args, tc.args...)
code, stdout, stderr := runCLICommand(t, renderCommand, args)
if code != ExitOK {
t.Fatalf("expected ExitOK, got %d stderr=%q", code, stderr)
}
if !strings.Contains(stdout, "session_id: "+tc.wantSessionID) {
t.Fatalf("expected session ID %q, got:\n%s", tc.wantSessionID, stdout)
}
hasReasoning := strings.Contains(stdout, "reasoning_effort:")
if tc.absentReasoning {
if hasReasoning {
t.Fatalf("expected cleared reasoning to be omitted, got:\n%s", stdout)
}
return
}
if !strings.Contains(stdout, "reasoning_effort: "+tc.wantReasoning) {
t.Fatalf("expected reasoning effort %q, got:\n%s", tc.wantReasoning, stdout)
}
})
}
}
func TestRenderCommandOmitsEmptyEffectiveSessionID(t *testing.T) {
lib := newCLITestLibrary(t)
writePromptDefinition(t, lib.promptDir, "plain.yaml", `id: plain
version: "1"
default_profile: local
messages:
- role: user
content: "hello"
output:
format: text
validation_mode: none
`)
writeProfileFile(t, lib.profileDir, "local", "http://127.0.0.1:1/v1", "local-model")
code, stdout, stderr := runCLICommand(t, renderCommand, []string{
"--prompt-dir", lib.promptDir,
"--profile-dir", lib.profileDir,
"--prompt", "plain",
})
if code != ExitOK {
t.Fatalf("expected ExitOK, got %d stderr=%q", code, stderr)
}
if strings.Contains(stdout, "session_id:") {
t.Fatalf("expected no effective session ID, got:\n%s", stdout)
}
}
func TestRenderCommandRejectsOverlongSessionID(t *testing.T) {
lib := newCLITestLibrary(t)
writePromptDefinition(t, lib.promptDir, "session.yaml", `id: session
version: "1"
default_profile: local
messages:
- role: user
content: "hello"
output:
format: text
validation_mode: none
`)
writeProfileFile(t, lib.profileDir, "local", "http://127.0.0.1:1/v1", "local-model")
code, _, stderr := runCLICommand(t, renderCommand, []string{
"--prompt-dir", lib.promptDir,
"--profile-dir", lib.profileDir,
"--prompt", "session",
"--session-id", strings.Repeat("x", 257),
})
if code != ExitRuntimeError {
t.Fatalf("expected runtime error, got %d stderr=%q", code, stderr)
}
if !strings.Contains(stderr, "invalid run request") {
t.Fatalf("expected invalid-request context, got %q", stderr)
}
}
func TestConfiguredBackendValidationComesFromPromptkit(t *testing.T) {
lib := newCLITestLibrary(t)