Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ docs/Agent.md
docs/AgentList.md
docs/AgentPatchRequest.md
docs/AgentSessionList.md
docs/ApiAmbientV1ProjectsIdScheduledSessionsSsIdTriggerPost200Response.md
docs/Credential.md
docs/CredentialList.md
docs/CredentialPatchRequest.md
Expand All @@ -35,6 +36,9 @@ docs/RoleBindingList.md
docs/RoleBindingPatchRequest.md
docs/RoleList.md
docs/RolePatchRequest.md
docs/ScheduledSession.md
docs/ScheduledSessionList.md
docs/ScheduledSessionPatchRequest.md
docs/Session.md
docs/SessionList.md
docs/SessionMessage.md
Expand All @@ -49,6 +53,7 @@ docs/UserPatchRequest.md
git_push.sh
go.mod
go.sum
model__api_ambient_v1_projects__id__scheduled_sessions__ss_id__trigger_post_200_response.go
model_agent.go
model_agent_list.go
model_agent_patch_request.go
Expand Down Expand Up @@ -77,6 +82,9 @@ model_role_binding_list.go
model_role_binding_patch_request.go
model_role_list.go
model_role_patch_request.go
model_scheduled_session.go
model_scheduled_session_list.go
model_scheduled_session_patch_request.go
model_session.go
model_session_list.go
model_session_message.go
Expand Down
2 changes: 1 addition & 1 deletion components/ambient-cli/cmd/acpctl/agent/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ This operation is idempotent — calling it multiple times is safe.`,
}

func startSingleAgent(ctx context.Context, cmd *cobra.Command, client *sdkclient.Client, projectID, agentID, displayName string) error {
resp, err := client.Agents().Start(ctx, projectID, agentID, agentStartArgs.prompt)
resp, err := client.Agents().StartInProject(ctx, projectID, agentID, agentStartArgs.prompt)
if err != nil {
return fmt.Errorf("start agent: %w", err)
}
Expand Down
19 changes: 5 additions & 14 deletions components/ambient-cli/cmd/acpctl/ambient/tui/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ func (tc *TUIClient) StartAgent(projectID, agentID, prompt string) tea.Cmd {
return StartAgentMsg{Err: err}
}

resp, err := client.Agents().Start(ctx, projectID, agentID, prompt)
resp, err := client.Agents().StartInProject(ctx, projectID, agentID, prompt)
if err != nil {
return StartAgentMsg{Err: err}
}
Expand Down Expand Up @@ -837,7 +837,7 @@ func (tc *TUIClient) FetchScheduledSessions(projectID string) tea.Cmd {
return ScheduledSessionsMsg{Err: err}
}

list, err := client.ScheduledSessions().List(ctx, projectID, defaultListOpts())
list, err := client.ScheduledSessions().ListByProject(ctx, projectID, defaultListOpts())
if err != nil {
return ScheduledSessionsMsg{Err: err}
}
Expand All @@ -856,7 +856,7 @@ func (tc *TUIClient) DeleteScheduledSession(projectID, id string) tea.Cmd {
return DeleteScheduledSessionMsg{Err: err}
}

err = client.ScheduledSessions().Delete(ctx, projectID, id)
err = client.ScheduledSessions().DeleteInProject(ctx, projectID, id)
return DeleteScheduledSessionMsg{Err: err}
}
}
Expand Down Expand Up @@ -936,7 +936,7 @@ func (tc *TUIClient) CreateScheduledSession(projectID, name, agentID, schedule,
Enabled: true,
}

result, err := client.ScheduledSessions().Create(ctx, projectID, ss)
result, err := client.ScheduledSessions().CreateInProject(ctx, projectID, ss)
if err != nil {
return CreateScheduledSessionMsg{Err: err}
}
Expand All @@ -955,16 +955,7 @@ func (tc *TUIClient) UpdateScheduledSession(projectID, id string, patch map[stri
return UpdateScheduledSessionMsg{Err: err}
}

patchJSON, err := json.Marshal(patch)
if err != nil {
return UpdateScheduledSessionMsg{Err: fmt.Errorf("marshal patch: %w", err)}
}
var typedPatch sdktypes.ScheduledSessionPatch
if err := json.Unmarshal(patchJSON, &typedPatch); err != nil {
return UpdateScheduledSessionMsg{Err: fmt.Errorf("unmarshal patch: %w", err)}
}

result, err := client.ScheduledSessions().Update(ctx, projectID, id, &typedPatch)
result, err := client.ScheduledSessions().UpdateInProject(ctx, projectID, id, patch)
if err != nil {
return UpdateScheduledSessionMsg{Err: err}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,9 @@ func ScheduledSessionDetail(ss sdktypes.ScheduledSession) []DetailLine {
updatedAt = ss.UpdatedAt.Format(time.RFC3339)
}

timeout := ""
if ss.Timeout != nil {
timeout = fmt.Sprintf("%d", *ss.Timeout)
}

inactivityTimeout := ""
if ss.InactivityTimeout != nil {
inactivityTimeout = fmt.Sprintf("%d", *ss.InactivityTimeout)
}

stopOnRunFinished := ""
if ss.StopOnRunFinished != nil {
stopOnRunFinished = fmt.Sprintf("%v", *ss.StopOnRunFinished)
}
timeout := fmt.Sprintf("%d", ss.Timeout)
inactivityTimeout := fmt.Sprintf("%d", ss.InactivityTimeout)
stopOnRunFinished := fmt.Sprintf("%v", ss.StopOnRunFinished)

return []DetailLine{
{Key: "ID", Value: ss.ID},
Expand Down
12 changes: 6 additions & 6 deletions components/ambient-cli/cmd/acpctl/scheduledsession/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func resolveScheduledSession(ctx context.Context, projectID, arg string) (string
if err != nil {
return "", err
}
ss, err := client.ScheduledSessions().Get(ctx, projectID, arg)
ss, err := client.ScheduledSessions().GetByProject(ctx, projectID, arg)
if err != nil {
ss, err = client.ScheduledSessions().GetByName(ctx, projectID, arg)
if err != nil {
Expand Down Expand Up @@ -98,7 +98,7 @@ var listCmd = &cobra.Command{
defer cancel()

opts := sdktypes.NewListOptions().Size(listArgs.limit).Build()
list, err := client.ScheduledSessions().List(ctx, projectID, opts)
list, err := client.ScheduledSessions().ListByProject(ctx, projectID, opts)
if err != nil {
return fmt.Errorf("list scheduled sessions: %w", err)
}
Expand Down Expand Up @@ -150,7 +150,7 @@ var getCmd = &cobra.Command{
ctx, cancel := context.WithTimeout(context.Background(), cfg.GetRequestTimeout())
defer cancel()

ss, err := client.ScheduledSessions().Get(ctx, projectID, args[0])
ss, err := client.ScheduledSessions().GetByProject(ctx, projectID, args[0])
if err != nil {
ss, err = client.ScheduledSessions().GetByName(ctx, projectID, args[0])
if err != nil {
Expand Down Expand Up @@ -250,7 +250,7 @@ var createCmd = &cobra.Command{
return fmt.Errorf("build scheduled session: %w", err)
}

created, err := client.ScheduledSessions().Create(ctx, projectID, ss)
created, err := client.ScheduledSessions().CreateInProject(ctx, projectID, ss)
if err != nil {
return fmt.Errorf("create scheduled session: %w", err)
}
Expand Down Expand Up @@ -349,7 +349,7 @@ var updateCmd = &cobra.Command{
patch = patch.RunnerType(updateArgs.runnerType)
}

updated, err := client.ScheduledSessions().Update(ctx, projectID, id, patch.Build())
updated, err := client.ScheduledSessions().UpdateInProject(ctx, projectID, id, patch.Build())
if err != nil {
return fmt.Errorf("update scheduled session: %w", err)
}
Expand Down Expand Up @@ -401,7 +401,7 @@ var deleteCmd = &cobra.Command{
return err
}

if err := client.ScheduledSessions().Delete(ctx, projectID, id); err != nil {
if err := client.ScheduledSessions().DeleteInProject(ctx, projectID, id); err != nil {
return fmt.Errorf("delete scheduled session: %w", err)
}

Expand Down
2 changes: 1 addition & 1 deletion components/ambient-cli/cmd/acpctl/start/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func run(cmd *cobra.Command, cmdArgs []string) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err := client.Agents().Start(ctx, startArgs.projectID, paID, startArgs.prompt)
resp, err := client.Agents().StartInProject(ctx, startArgs.projectID, paID, startArgs.prompt)
if err != nil {
return fmt.Errorf("start agent %q: %w", paID, err)
}
Expand Down
56 changes: 40 additions & 16 deletions components/ambient-sdk/generator/templates/go/http_client.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

type Client struct {
httpClient *http.Client
streamingClient *http.Client
baseURL string
token string
project string
Expand All @@ -44,17 +45,21 @@ func WithTimeout(timeout time.Duration) ClientOption {
func WithInsecureSkipVerify() ClientOption {
return func(c *Client) {
c.insecureSkipVerify = true
t, ok := c.httpClient.Transport.(*http.Transport)
if !ok || t == nil {
t = http.DefaultTransport.(*http.Transport).Clone()
} else {
t = t.Clone()
applyInsecure := func(hc *http.Client) {
t, ok := hc.Transport.(*http.Transport)
if !ok || t == nil {
t = http.DefaultTransport.(*http.Transport).Clone()
} else {
t = t.Clone()
}
if t.TLSClientConfig == nil {
t.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12}
}
t.TLSClientConfig.InsecureSkipVerify = true //nolint:gosec
hc.Transport = t
}
if t.TLSClientConfig == nil {
t.TLSClientConfig = &tls.Config{MinVersion: tls.VersionTLS12}
}
t.TLSClientConfig.InsecureSkipVerify = true //nolint:gosec
c.httpClient.Transport = t
applyInsecure(c.httpClient)
applyInsecure(c.streamingClient)
}
}

Expand Down Expand Up @@ -95,15 +100,19 @@ func NewClient(baseURL, token, project string, opts ...ClientOption) (*Client, e
return nil, fmt.Errorf("invalid base URL: %w", err)
}

streamingTransport := http.DefaultTransport.(*http.Transport).Clone()
streamingTransport.DisableCompression = true

c := &Client{
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
baseURL: strings.TrimSuffix(baseURL, "/"),
token: token,
project: project,
logger: slog.Default(),
userAgent: "ambient-go-sdk/1.0.0",
streamingClient: &http.Client{Transport: streamingTransport},
baseURL: strings.TrimSuffix(baseURL, "/"),
token: token,
project: project,
logger: slog.Default(),
userAgent: "ambient-go-sdk/1.0.0",
}

for _, opt := range opts {
Expand All @@ -115,6 +124,10 @@ func NewClient(baseURL, token, project string, opts ...ClientOption) (*Client, e
return c, nil
}

func (c *Client) Project() string {
return c.project
}

func NewClientFromEnv(opts ...ClientOption) (*Client, error) {
baseURL := os.Getenv("AMBIENT_API_URL")
if baseURL == "" {
Expand All @@ -135,6 +148,10 @@ func NewClientFromEnv(opts ...ClientOption) (*Client, error) {
}

func (c *Client) do(ctx context.Context, method, path string, body []byte, expectedStatus int, result interface{}) error {
return c.doMultiStatus(ctx, method, path, body, result, expectedStatus)
}

func (c *Client) doMultiStatus(ctx context.Context, method, path string, body []byte, result interface{}, expectedStatuses ...int) error {
url := c.baseURL + "{{.Spec.BasePath}}" + path

req, err := http.NewRequestWithContext(ctx, method, url, nil)
Expand Down Expand Up @@ -175,7 +192,14 @@ func (c *Client) do(ctx context.Context, method, path string, body []byte, expec
slog.Int("body_len", len(respBody)),
)

if resp.StatusCode != expectedStatus {
statusOK := false
for _, s := range expectedStatuses {
if resp.StatusCode == s {
statusOK = true
break
}
}
if !statusOK {
var apiErr types.APIError
if json.Unmarshal(respBody, &apiErr) == nil && apiErr.Code != "" {
apiErr.StatusCode = resp.StatusCode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,29 @@ export class AmbientClient {
if (!config.baseUrl) {
throw new Error('baseUrl is required');
}
if (!config.token) {
throw new Error('token is required');
if (config.token) {
if (config.token.length < 20) {
throw new Error('token is too short (minimum 20 characters)');
}
if (config.token === 'YOUR_TOKEN_HERE' || config.token === 'PLACEHOLDER_TOKEN') {
throw new Error('placeholder token is not allowed');
}
}
if (config.token.length < 20) {
throw new Error('token is too short (minimum 20 characters)');
}
if (config.token === 'YOUR_TOKEN_HERE' || config.token === 'PLACEHOLDER_TOKEN') {
throw new Error('placeholder token is not allowed');
}
if (!config.project) {
throw new Error('project is required');
}
if (config.project.length > 63) {
if (config.project && config.project.length > 63) {
throw new Error('project name cannot exceed 63 characters');
}

const url = new URL(config.baseUrl);
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
throw new Error('only HTTP and HTTPS schemes are supported');
// Reject protocol-relative URLs (e.g. "//evil.com/path")
if (config.baseUrl.startsWith('//')) {
throw new Error('Protocol-relative URLs are not allowed for baseUrl');
}

// Skip URL parsing for relative paths (e.g. "/api/proxy")
if (!config.baseUrl.startsWith('/')) {
const url = new URL(config.baseUrl);
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
throw new Error('only HTTP and HTTPS schemes are supported');
}
}

this.config = {
Expand All @@ -54,10 +58,7 @@ export class AmbientClient {
if (!token) {
throw new Error('AMBIENT_TOKEN environment variable is required');
}
if (!project) {
throw new Error('AMBIENT_PROJECT environment variable is required');
}

return new AmbientClient({ baseUrl, token, project });
return new AmbientClient({ baseUrl, token, ...(project ? { project } : {}) });
}
}
8 changes: 4 additions & 4 deletions components/ambient-sdk/generator/templates/ts/base.ts.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export type RequestOptions = {

export type AmbientClientConfig = {
baseUrl: string;
token: string;
project: string;
token?: string;
project?: string;
};

export async function ambientFetch<T>(
Expand All @@ -83,8 +83,8 @@ export async function ambientFetch<T>(
): Promise<T> {
const url = `${config.baseUrl}{{.Spec.BasePath}}${path}`;
const headers: Record<string, string> = {
'Authorization': `Bearer ${config.token}`,
'X-Ambient-Project': config.project,
...(config.token ? { 'Authorization': `Bearer ${config.token}` } : {}),
...(config.project ? { 'X-Ambient-Project': config.project } : {}),
};
if (body !== undefined) {
headers['Content-Type'] = 'application/json';
Expand Down
3 changes: 3 additions & 0 deletions components/ambient-sdk/generator/templates/ts/client.ts.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export class {{.Resource.Name}}API {

{{- if .Resource.IsSubResource}}
private basePath(): string {
if (!this.config.project) {
throw new Error('project is required for {{.Resource.Name}} operations');
}
return '/{{.Resource.PathSegment}}'.replace('{id}', encodeURIComponent(this.config.project));
}
{{end}}
Expand Down
Loading
Loading