Skip to content

Commit a749192

Browse files
brettheapwarp-agent
andcommitted
Add local development configuration and openspec documentation
- Add .claude/ directory for Claude Code configuration - Add openspec/ directory for project specifications These are for local/fork development use only. Co-Authored-By: Warp <agent@warp.dev>
1 parent 0222658 commit a749192

6 files changed

Lines changed: 431 additions & 0 deletions

File tree

.claude/commands/openspec/apply.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: OpenSpec: Apply
3+
description: Implement an approved OpenSpec change and keep tasks in sync.
4+
category: OpenSpec
5+
tags: [openspec, apply]
6+
---
7+
<!-- OPENSPEC:START -->
8+
**Guardrails**
9+
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
10+
- Keep changes tightly scoped to the requested outcome.
11+
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
12+
13+
**Steps**
14+
Track these steps as TODOs and complete them one by one.
15+
1. Read `changes/<id>/proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria.
16+
2. Work through tasks sequentially, keeping edits minimal and focused on the requested change.
17+
3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished.
18+
4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality.
19+
5. Reference `openspec list` or `openspec show <item>` when additional context is required.
20+
21+
**Reference**
22+
- Use `openspec show <id> --json --deltas-only` if you need additional context from the proposal while implementing.
23+
<!-- OPENSPEC:END -->
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
name: OpenSpec: Archive
3+
description: Archive a deployed OpenSpec change and update specs.
4+
category: OpenSpec
5+
tags: [openspec, archive]
6+
---
7+
<!-- OPENSPEC:START -->
8+
**Guardrails**
9+
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
10+
- Keep changes tightly scoped to the requested outcome.
11+
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
12+
13+
**Steps**
14+
1. Determine the change ID to archive:
15+
- If this prompt already includes a specific change ID (for example inside a `<ChangeId>` block populated by slash-command arguments), use that value after trimming whitespace.
16+
- If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends.
17+
- Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding.
18+
- If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet.
19+
2. Validate the change ID by running `openspec list` (or `openspec show <id>`) and stop if the change is missing, already archived, or otherwise not ready to archive.
20+
3. Run `openspec archive <id> --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work).
21+
4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`.
22+
5. Validate with `openspec validate --strict` and inspect with `openspec show <id>` if anything looks off.
23+
24+
**Reference**
25+
- Use `openspec list` to confirm change IDs before archiving.
26+
- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off.
27+
<!-- OPENSPEC:END -->
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
name: OpenSpec: Proposal
3+
description: Scaffold a new OpenSpec change and validate strictly.
4+
category: OpenSpec
5+
tags: [openspec, change]
6+
---
7+
<!-- OPENSPEC:START -->
8+
**Guardrails**
9+
- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required.
10+
- Keep changes tightly scoped to the requested outcome.
11+
- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications.
12+
- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files.
13+
- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval.
14+
15+
**Steps**
16+
1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification.
17+
2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes/<id>/`.
18+
3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing.
19+
4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs.
20+
5. Draft spec deltas in `changes/<id>/specs/<capability>/spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant.
21+
6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work.
22+
7. Validate with `openspec validate <id> --strict` and resolve every issue before sharing the proposal.
23+
24+
**Reference**
25+
- Use `openspec show <id> --json --deltas-only` or `openspec show <spec> --type spec` to inspect details when validation fails.
26+
- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones.
27+
- Explore the codebase with `rg <keyword>`, `ls`, or direct file reads so proposals align with current implementation realities.
28+
<!-- OPENSPEC:END -->
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Change: Validate provider baseURL format before passing to SDK
2+
3+
## Why
4+
5+
When users misconfigure their `opencode.json` with an invalid `api` field (e.g., `"api": "anthropic"` instead of a URL), or when invalid values slip through the options merge chain, the SDK receives a malformed `baseURL`. The `@ai-sdk/*` SDKs use nullish coalescing (`??`) for defaults, which doesn't catch empty strings or non-URL values—causing cryptic `ERR_INVALID_URL` errors from `fetch()`.
6+
7+
## What Changes
8+
9+
### Phase 1: Runtime Validation (Complete)
10+
11+
- Add URL format validation before passing `baseURL` to provider SDKs
12+
- Only set `baseURL` if it's a valid URL (starts with `http://` or `https://`)
13+
- Delete invalid `baseURL` values so SDKs fall back to their hardcoded defaults
14+
15+
### Phase 2: Config-Time Validation (Planned)
16+
17+
- Add validation at config load time to catch invalid values early
18+
- Hybrid approach: validate against models.dev data when available, fall back to URL format check
19+
- Provide clear error messages pointing users to the exact config issue
20+
21+
## Impact
22+
23+
- Affected code:
24+
- Phase 1: `packages/opencode/src/provider/provider.ts`
25+
- Phase 2: `packages/opencode/src/config/config.ts`
26+
- Risk: Low - only filters out invalid URLs, cannot break valid configurations
27+
- User benefit: Clear failure mode instead of cryptic fetch errors
28+
29+
## Root Cause Analysis
30+
31+
### The Problem
32+
33+
The `@ai-sdk/anthropic` SDK (and others) construct their baseURL like this:
34+
35+
```javascript
36+
const baseURL = withoutTrailingSlash(loadOptionalSetting({
37+
settingValue: options.baseURL,
38+
environmentVariableName: "ANTHROPIC_BASE_URL"
39+
})) ?? "https://api.anthropic.com/v1";
40+
```
41+
42+
The `??` operator only catches `null`/`undefined`. If `baseURL` is:
43+
- Empty string `""`
44+
- Invalid value like `"anthropic"`
45+
46+
...it bypasses the default and causes `fetch()` to fail with `ERR_INVALID_URL`.
47+
48+
### How Invalid Values Reach the SDK
49+
50+
1. **User config misconfiguration**: `"api": "anthropic"` instead of a URL
51+
2. **models.dev API**: Returns `"api": null` for some providers
52+
3. **Options merge chain**: Various sources merged without validation
53+
54+
## Phase 1: Runtime Fix (Complete)
55+
56+
```typescript
57+
const isValidUrl = (url: string | undefined | null): url is string => {
58+
return typeof url === "string" && (url.startsWith("http://") || url.startsWith("https://"))
59+
}
60+
if (!isValidUrl(options["baseURL"])) {
61+
delete options["baseURL"]
62+
if (isValidUrl(model.api.url)) options["baseURL"] = model.api.url
63+
}
64+
```
65+
66+
This ensures only valid URLs reach the SDK; otherwise, the SDK uses its default.
67+
68+
## Phase 2: Config-Time Validation Design
69+
70+
### Validation Flow
71+
72+
```
73+
┌─────────────────────────────────────────────────────────┐
74+
│ Config Validation Flow │
75+
├─────────────────────────────────────────────────────────┤
76+
│ │
77+
1. Load config file (JSONC parse) │
78+
│ │ │
79+
│ ▼ │
80+
2. Zod schema validation (Info.safeParse) │
81+
│ │ │
82+
│ ▼ │
83+
3. Post-validation hook (NEW) │
84+
│ ┌───────────────┴───────────────┐ │
85+
│ │ │ │
86+
│ ▼ ▼ │
87+
│ Try models.dev cache Cache unavailable?
88+
│ │ │ │
89+
│ ▼ ▼ │
90+
│ Validate against URL format check │
91+
│ known providers (http/https prefix) │
92+
│ │ │ │
93+
│ └───────────────┬───────────────┘ │
94+
│ ▼ │
95+
4. Return validated config or throw error │
96+
│ │
97+
└─────────────────────────────────────────────────────────┘
98+
```
99+
100+
### Hybrid Validation Strategy
101+
102+
**Primary: Validate against models.dev data**
103+
104+
When models.dev cache is available (`~/.cache/opencode/models.json`):
105+
- Check if provider `api` field matches expected format for known providers
106+
- Known providers with dedicated SDKs (anthropic, openai, groq) expect `null` or valid URL
107+
- Other providers from models.dev show what valid `api` URLs look like
108+
109+
**Fallback: URL format validation**
110+
111+
When models.dev cache is unavailable:
112+
- If `api` field is set, validate it starts with `http://` or `https://`
113+
- Reject obviously invalid values like `"anthropic"`, `""`, or random strings
114+
115+
### What models.dev Returns
116+
117+
| Provider Type | `api` Field Value |
118+
|---------------|-------------------|
119+
| SDK-based (anthropic, openai) | `null` or not set |
120+
| URL-based (moonshot, nvidia) | `"https://api.example.com/v1"` |
121+
122+
### Implementation Location
123+
124+
Post-validation in `Config.loadFile()` after `Info.safeParse()`:
125+
126+
```typescript
127+
// After line 963: const parsed = Info.safeParse(data)
128+
if (parsed.success && parsed.data.provider) {
129+
await validateProviderConfig(parsed.data.provider)
130+
}
131+
```
132+
133+
### Error Messages
134+
135+
```
136+
Config validation error at opencode.json:
137+
provider.anthropic.api: Invalid value "anthropic"
138+
Expected: Valid URL (https://...) or omit to use SDK default
139+
140+
Hint: Remove the "api" field to use the default Anthropic endpoint,
141+
or provide a valid proxy URL like "https://my-proxy.com/v1"
142+
```
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Provider baseURL Runtime Validation (Phase 1)
4+
5+
The system SHALL validate that `baseURL` values passed to provider SDKs are valid URLs (starting with `http://` or `https://`). Invalid or empty values SHALL be removed so the SDK can use its default endpoint.
6+
7+
#### Scenario: User misconfigures api field with provider name
8+
9+
- **WHEN** user sets `"api": "anthropic"` in config instead of a URL
10+
- **THEN** the invalid value is filtered out
11+
- **AND** the SDK uses its default baseURL (`https://api.anthropic.com/v1`)
12+
13+
#### Scenario: baseURL is empty string
14+
15+
- **WHEN** baseURL is set to empty string `""`
16+
- **THEN** the empty value is deleted from options
17+
- **AND** the SDK uses its default baseURL
18+
19+
#### Scenario: baseURL is null from models.dev
20+
21+
- **WHEN** models.dev API returns `"api": null` for a provider
22+
- **THEN** the null value does not override SDK defaults
23+
- **AND** the SDK uses its hardcoded default baseURL
24+
25+
#### Scenario: Valid baseURL is preserved
26+
27+
- **WHEN** baseURL is a valid URL like `"https://custom-proxy.example.com/v1"`
28+
- **THEN** the URL is passed to the SDK unchanged
29+
- **AND** the SDK uses the custom baseURL
30+
31+
---
32+
33+
### Requirement: Provider Config Validation at Load Time (Phase 2)
34+
35+
The system SHALL validate provider configuration fields when loading `opencode.json`, using a hybrid validation strategy that validates against models.dev data when available and falls back to URL format validation otherwise.
36+
37+
#### Scenario: Validate against models.dev data (primary)
38+
39+
- **GIVEN** models.dev cache is available at `~/.cache/opencode/models.json`
40+
- **WHEN** config contains a provider with an `api` field
41+
- **THEN** the system validates the `api` value against the expected format for that provider
42+
- **AND** rejects values that don't match (e.g., `"anthropic"` instead of a URL)
43+
44+
#### Scenario: Fallback to URL format validation
45+
46+
- **GIVEN** models.dev cache is NOT available
47+
- **WHEN** config contains a provider with an `api` field
48+
- **THEN** the system validates that `api` starts with `http://` or `https://`
49+
- **AND** rejects obviously invalid values like `"anthropic"` or `""`
50+
51+
#### Scenario: Clear error message on validation failure
52+
53+
- **WHEN** config validation fails due to invalid `api` value
54+
- **THEN** the error message SHALL include:
55+
- The exact field path (e.g., `provider.anthropic.api`)
56+
- The invalid value that was provided
57+
- A hint explaining valid options (URL or omit field)
58+
59+
#### Scenario: Valid config passes validation
60+
61+
- **WHEN** config contains valid provider configuration
62+
- **AND** `api` field is either omitted, null, or a valid URL
63+
- **THEN** validation passes without error
64+
- **AND** config is loaded normally
65+
66+
---
67+
68+
## Validation Flow
69+
70+
```
71+
┌─────────────────────────────────────────────────────────┐
72+
│ Config Validation Flow │
73+
├─────────────────────────────────────────────────────────┤
74+
│ │
75+
│ 1. Load config file (JSONC parse) │
76+
│ │ │
77+
│ ▼ │
78+
│ 2. Zod schema validation (Info.safeParse) │
79+
│ │ │
80+
│ ▼ │
81+
│ 3. Post-validation hook │
82+
│ ┌───────────────┴───────────────┐ │
83+
│ │ │ │
84+
│ ▼ ▼ │
85+
│ Try models.dev cache Cache unavailable? │
86+
│ │ │ │
87+
│ ▼ ▼ │
88+
│ Validate against URL format check │
89+
│ known providers (http/https prefix) │
90+
│ │ │ │
91+
│ └───────────────┬───────────────┘ │
92+
│ ▼ │
93+
│ 4. Return validated config or throw error │
94+
│ │
95+
└─────────────────────────────────────────────────────────┘
96+
```
97+
98+
## Defense in Depth
99+
100+
```
101+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
102+
│ Layer 1 │ │ Layer 2 │ │ Layer 3 │
103+
│ Config │────▶│ Runtime │────▶│ SDK │
104+
│ Validation │ │ Validation │ │ Defaults │
105+
│ (Phase 2) │ │ (Phase 1) │ │ (Fallback) │
106+
└─────────────┘ └─────────────┘ └─────────────┘
107+
│ │ │
108+
▼ ▼ ▼
109+
Catch at Catch at Last
110+
load time runtime resort
111+
```

0 commit comments

Comments
 (0)