orch-code 0.1.3 → 0.1.4
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/CHANGELOG.md +3 -0
- package/README.md +3 -0
- package/cmd/doctor.go +60 -18
- package/cmd/provider_model_doctor_test.go +81 -0
- package/cmd/version.go +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -333,8 +333,11 @@ Validate runtime readiness:
|
|
|
333
333
|
|
|
334
334
|
```bash
|
|
335
335
|
./orch doctor
|
|
336
|
+
./orch doctor --probe
|
|
336
337
|
```
|
|
337
338
|
|
|
339
|
+
`--probe` runs a small live OpenAI chat check, which is useful for validating account-mode OAuth auth beyond local token shape checks.
|
|
340
|
+
|
|
338
341
|
Generate a structured plan only:
|
|
339
342
|
|
|
340
343
|
```bash
|
package/cmd/doctor.go
CHANGED
|
@@ -9,10 +9,13 @@ import (
|
|
|
9
9
|
|
|
10
10
|
"github.com/furkanbeydemir/orch/internal/auth"
|
|
11
11
|
"github.com/furkanbeydemir/orch/internal/config"
|
|
12
|
+
"github.com/furkanbeydemir/orch/internal/providers"
|
|
12
13
|
"github.com/furkanbeydemir/orch/internal/providers/openai"
|
|
13
14
|
"github.com/spf13/cobra"
|
|
14
15
|
)
|
|
15
16
|
|
|
17
|
+
var doctorProbeFlag bool
|
|
18
|
+
|
|
16
19
|
var doctorCmd = &cobra.Command{
|
|
17
20
|
Use: "doctor",
|
|
18
21
|
Short: "Validate Orch runtime readiness",
|
|
@@ -21,6 +24,7 @@ var doctorCmd = &cobra.Command{
|
|
|
21
24
|
|
|
22
25
|
func init() {
|
|
23
26
|
rootCmd.AddCommand(doctorCmd)
|
|
27
|
+
doctorCmd.Flags().BoolVar(&doctorProbeFlag, "probe", false, "Run a live provider chat probe")
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
func runDoctor(cmd *cobra.Command, args []string) error {
|
|
@@ -85,8 +89,8 @@ func runDoctor(cmd *cobra.Command, args []string) error {
|
|
|
85
89
|
})
|
|
86
90
|
|
|
87
91
|
checks = append(checks, check{
|
|
88
|
-
name:
|
|
89
|
-
ok:
|
|
92
|
+
name: "openai.account_refresh",
|
|
93
|
+
ok: authMode != "account" || accountToken != "" || !storedAccount || storedRefresh || (storedCred != nil && storedCred.ExpiresAt.IsZero()) || time.Now().UTC().Before(storedCred.ExpiresAt),
|
|
90
94
|
detail: fmt.Sprintf("required_when_expired=%t", authMode == "account" && accountToken == ""),
|
|
91
95
|
})
|
|
92
96
|
|
|
@@ -95,30 +99,27 @@ func runDoctor(cmd *cobra.Command, args []string) error {
|
|
|
95
99
|
checks = append(checks, check{name: "openai.model.reviewer", ok: strings.TrimSpace(cfg.Provider.OpenAI.Models.Reviewer) != "", detail: cfg.Provider.OpenAI.Models.Reviewer})
|
|
96
100
|
|
|
97
101
|
if cfg.Provider.Flags.OpenAIEnabled && defaultProvider == "openai" {
|
|
98
|
-
client :=
|
|
99
|
-
client.SetTokenResolver(func(ctx context.Context) (string, error) {
|
|
100
|
-
_ = ctx
|
|
101
|
-
if authMode == "api_key" {
|
|
102
|
-
if storedCred != nil && strings.TrimSpace(storedCred.Key) != "" {
|
|
103
|
-
return strings.TrimSpace(storedCred.Key), nil
|
|
104
|
-
}
|
|
105
|
-
return "", nil
|
|
106
|
-
}
|
|
107
|
-
resolved, resolveErr := auth.ResolveAccountAccessToken(cwd, "openai")
|
|
108
|
-
if resolveErr != nil {
|
|
109
|
-
return "", resolveErr
|
|
110
|
-
}
|
|
111
|
-
return resolved, nil
|
|
112
|
-
})
|
|
102
|
+
client := newDoctorOpenAIClient(cwd, cfg.Provider.OpenAI, authMode, storedCred)
|
|
113
103
|
|
|
114
104
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
115
105
|
defer cancel()
|
|
116
106
|
validateErr := client.Validate(ctx)
|
|
117
107
|
checks = append(checks, check{
|
|
118
|
-
name: "openai.auth",
|
|
108
|
+
name: "openai.auth.local",
|
|
119
109
|
ok: validateErr == nil,
|
|
120
110
|
detail: errDetail(validateErr, "ok"),
|
|
121
111
|
})
|
|
112
|
+
|
|
113
|
+
if doctorProbeFlag {
|
|
114
|
+
probeCtx, probeCancel := context.WithTimeout(context.Background(), doctorProbeTimeout(cfg.Provider.OpenAI.TimeoutSeconds))
|
|
115
|
+
defer probeCancel()
|
|
116
|
+
probeErr := runOpenAIProbe(probeCtx, client, cfg.Provider.OpenAI.Models.Coder)
|
|
117
|
+
checks = append(checks, check{
|
|
118
|
+
name: "openai.auth.probe",
|
|
119
|
+
ok: probeErr == nil,
|
|
120
|
+
detail: errDetail(probeErr, "ok"),
|
|
121
|
+
})
|
|
122
|
+
}
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
failed := 0
|
|
@@ -147,3 +148,44 @@ func errDetail(err error, fallback string) string {
|
|
|
147
148
|
}
|
|
148
149
|
return err.Error()
|
|
149
150
|
}
|
|
151
|
+
|
|
152
|
+
func newDoctorOpenAIClient(cwd string, cfg config.OpenAIProviderConfig, authMode string, storedCred *auth.Credential) *openai.Client {
|
|
153
|
+
client := openai.New(cfg)
|
|
154
|
+
client.SetTokenResolver(func(ctx context.Context) (string, error) {
|
|
155
|
+
_ = ctx
|
|
156
|
+
if authMode == "api_key" {
|
|
157
|
+
if storedCred != nil && strings.TrimSpace(storedCred.Key) != "" {
|
|
158
|
+
return strings.TrimSpace(storedCred.Key), nil
|
|
159
|
+
}
|
|
160
|
+
return "", nil
|
|
161
|
+
}
|
|
162
|
+
resolved, resolveErr := auth.ResolveAccountAccessToken(cwd, "openai")
|
|
163
|
+
if resolveErr != nil {
|
|
164
|
+
return "", resolveErr
|
|
165
|
+
}
|
|
166
|
+
return resolved, nil
|
|
167
|
+
})
|
|
168
|
+
return client
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
func runOpenAIProbe(ctx context.Context, client *openai.Client, model string) error {
|
|
172
|
+
_, err := client.Chat(ctx, providers.ChatRequest{
|
|
173
|
+
Role: providers.RoleCoder,
|
|
174
|
+
Model: strings.TrimSpace(model),
|
|
175
|
+
SystemPrompt: "Reply with OK only.",
|
|
176
|
+
UserPrompt: "ping",
|
|
177
|
+
ReasoningEffort: "low",
|
|
178
|
+
})
|
|
179
|
+
return err
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
func doctorProbeTimeout(timeoutSeconds int) time.Duration {
|
|
183
|
+
if timeoutSeconds <= 0 {
|
|
184
|
+
return 20 * time.Second
|
|
185
|
+
}
|
|
186
|
+
timeout := time.Duration(timeoutSeconds) * time.Second
|
|
187
|
+
if timeout > 20*time.Second {
|
|
188
|
+
return 20 * time.Second
|
|
189
|
+
}
|
|
190
|
+
return timeout
|
|
191
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
package cmd
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"encoding/base64"
|
|
4
5
|
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net/http"
|
|
8
|
+
"net/http/httptest"
|
|
5
9
|
"strings"
|
|
6
10
|
"testing"
|
|
7
11
|
|
|
@@ -56,6 +60,76 @@ func TestDoctorFailsWithoutAPIKey(t *testing.T) {
|
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
func TestDoctorProbeAccountModeSucceeds(t *testing.T) {
|
|
64
|
+
repoRoot := t.TempDir()
|
|
65
|
+
t.Chdir(repoRoot)
|
|
66
|
+
|
|
67
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
68
|
+
if r.URL.Path != "/codex/responses" {
|
|
69
|
+
t.Fatalf("unexpected path: %s", r.URL.Path)
|
|
70
|
+
}
|
|
71
|
+
if got := r.Header.Get("ChatGPT-Account-Id"); got != "acc-123" {
|
|
72
|
+
t.Fatalf("unexpected account header: %s", got)
|
|
73
|
+
}
|
|
74
|
+
w.Header().Set("Content-Type", "application/json")
|
|
75
|
+
_, _ = w.Write([]byte(`{"output_text":"OK","status":"completed","usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}`))
|
|
76
|
+
}))
|
|
77
|
+
defer server.Close()
|
|
78
|
+
|
|
79
|
+
if err := config.EnsureOrchDir(repoRoot); err != nil {
|
|
80
|
+
t.Fatalf("ensure orch dir: %v", err)
|
|
81
|
+
}
|
|
82
|
+
cfg := config.DefaultConfig()
|
|
83
|
+
cfg.Provider.OpenAI.AuthMode = "account"
|
|
84
|
+
cfg.Provider.OpenAI.BaseURL = server.URL
|
|
85
|
+
cfg.Provider.OpenAI.TimeoutSeconds = 5
|
|
86
|
+
if err := config.Save(repoRoot, cfg); err != nil {
|
|
87
|
+
t.Fatalf("save config: %v", err)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
t.Setenv("OPENAI_ACCOUNT_TOKEN", testDoctorAccountToken("acc-123"))
|
|
91
|
+
doctorProbeFlag = true
|
|
92
|
+
defer func() { doctorProbeFlag = false }()
|
|
93
|
+
|
|
94
|
+
if err := runDoctor(nil, nil); err != nil {
|
|
95
|
+
t.Fatalf("expected doctor probe to succeed: %v", err)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func TestDoctorProbeAccountModeFailsWhenProviderRejects(t *testing.T) {
|
|
100
|
+
repoRoot := t.TempDir()
|
|
101
|
+
t.Chdir(repoRoot)
|
|
102
|
+
|
|
103
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
104
|
+
w.WriteHeader(http.StatusUnauthorized)
|
|
105
|
+
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
|
|
106
|
+
}))
|
|
107
|
+
defer server.Close()
|
|
108
|
+
|
|
109
|
+
if err := config.EnsureOrchDir(repoRoot); err != nil {
|
|
110
|
+
t.Fatalf("ensure orch dir: %v", err)
|
|
111
|
+
}
|
|
112
|
+
cfg := config.DefaultConfig()
|
|
113
|
+
cfg.Provider.OpenAI.AuthMode = "account"
|
|
114
|
+
cfg.Provider.OpenAI.BaseURL = server.URL
|
|
115
|
+
cfg.Provider.OpenAI.TimeoutSeconds = 5
|
|
116
|
+
if err := config.Save(repoRoot, cfg); err != nil {
|
|
117
|
+
t.Fatalf("save config: %v", err)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
t.Setenv("OPENAI_ACCOUNT_TOKEN", testDoctorAccountToken("acc-123"))
|
|
121
|
+
doctorProbeFlag = true
|
|
122
|
+
defer func() { doctorProbeFlag = false }()
|
|
123
|
+
|
|
124
|
+
err := runDoctor(nil, nil)
|
|
125
|
+
if err == nil {
|
|
126
|
+
t.Fatalf("expected doctor probe failure")
|
|
127
|
+
}
|
|
128
|
+
if !strings.Contains(err.Error(), "doctor failed") {
|
|
129
|
+
t.Fatalf("unexpected doctor error: %v", err)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
59
133
|
func TestProviderListJSONOutput(t *testing.T) {
|
|
60
134
|
repoRoot := t.TempDir()
|
|
61
135
|
t.Chdir(repoRoot)
|
|
@@ -89,3 +163,10 @@ func TestProviderListJSONOutput(t *testing.T) {
|
|
|
89
163
|
t.Fatalf("expected openai in all providers, got: %#v", all)
|
|
90
164
|
}
|
|
91
165
|
}
|
|
166
|
+
|
|
167
|
+
func testDoctorAccountToken(accountID string) string {
|
|
168
|
+
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"none"}`))
|
|
169
|
+
payload := fmt.Sprintf(`{"https://api.openai.com/auth":{"chatgpt_account_id":"%s"}}`, accountID)
|
|
170
|
+
body := base64.RawURLEncoding.EncodeToString([]byte(payload))
|
|
171
|
+
return header + "." + body + ".sig"
|
|
172
|
+
}
|
package/cmd/version.go
CHANGED