Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.6.1"
".": "0.6.2"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 16
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-b019e469425a59061f37c5fdc7a131a5291c66134ef0627db4f06bb1f4af0b15.yml
openapi_spec_hash: f66a3c2efddb168db9539ba2507b10b8
config_hash: aae6721b2be9ec8565dfc8f7eadfe105
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-2aec229ccf91f7c1ac95aa675ea2a59bd61af9e363a22c3b49677992f1eeb16a.yml
openapi_spec_hash: c80cd5d52a79cd5366a76d4a825bd27a
config_hash: b8e1fff080fbaa22656ab0a57b591777
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.6.2 (2025-06-24)

Full Changelog: [v0.6.1...v0.6.2](https://github.com/onkernel/kernel-go-sdk/compare/v0.6.1...v0.6.2)

### Features

* **api:** add `since` parameter to deployment logs endpoint ([dc72c81](https://github.com/onkernel/kernel-go-sdk/commit/dc72c81a2e45918d595c6c00843b1a1d0efffdd0))
* **client:** add escape hatch for null slice & maps ([d5e1ad9](https://github.com/onkernel/kernel-go-sdk/commit/d5e1ad9087aecd6b67369b9ebbeb633ad808c129))


### Chores

* fix documentation of null map ([a62b964](https://github.com/onkernel/kernel-go-sdk/commit/a62b9647386501f43e70ad876dc5c0271c4c4709))

## 0.6.1 (2025-06-18)

Full Changelog: [v0.6.0...v0.6.1](https://github.com/onkernel/kernel-go-sdk/compare/v0.6.0...v0.6.1)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Or to pin the version:
<!-- x-release-please-start-version -->

```sh
go get -u 'github.com/onkernel/kernel-go-sdk@v0.6.1'
go get -u 'github.com/onkernel/kernel-go-sdk@v0.6.2'
```

<!-- x-release-please-end -->
Expand Down
5 changes: 5 additions & 0 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ type ErrorEvent = shared.ErrorEvent
// This is an alias to an internal type.
type ErrorModel = shared.ErrorModel

// Heartbeat event sent periodically to keep SSE connection alive.
//
// This is an alias to an internal type.
type HeartbeatEvent = shared.HeartbeatEvent

// A log entry from the application.
//
// This is an alias to an internal type.
Expand Down
3 changes: 2 additions & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared">shared</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared#ErrorDetail">ErrorDetail</a>
- <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared">shared</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared#ErrorEvent">ErrorEvent</a>
- <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared">shared</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared#ErrorModel">ErrorModel</a>
- <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared">shared</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared#HeartbeatEvent">HeartbeatEvent</a>
- <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared">shared</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk/shared#LogEvent">LogEvent</a>

# Deployments
Expand All @@ -18,7 +19,7 @@ Methods:

- <code title="post /deployments">client.Deployments.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentNewParams">DeploymentNewParams</a>) (<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentNewResponse">DeploymentNewResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /deployments/{id}">client.Deployments.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentGetResponse">DeploymentGetResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /deployments/{id}/events">client.Deployments.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentService.Follow">Follow</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentFollowResponseUnion">DeploymentFollowResponseUnion</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /deployments/{id}/events">client.Deployments.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentService.Follow">Follow</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentFollowParams">DeploymentFollowParams</a>) (<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/onkernel/kernel-go-sdk#DeploymentFollowResponseUnion">DeploymentFollowResponseUnion</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>

# Apps

Expand Down
3 changes: 3 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type AppListResponse struct {
ID string `json:"id,required"`
// Name of the application
AppName string `json:"app_name,required"`
// Deployment ID
Deployment string `json:"deployment,required"`
// Deployment region code
Region constant.AwsUsEast1a `json:"region,required"`
// Version label for the application
Expand All @@ -61,6 +63,7 @@ type AppListResponse struct {
JSON struct {
ID respjson.Field
AppName respjson.Field
Deployment respjson.Field
Region respjson.Field
Version respjson.Field
EnvVars respjson.Field
Expand Down
13 changes: 11 additions & 2 deletions appdeployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,15 @@ const (

// AppDeploymentFollowResponseUnion contains all possible properties and values
// from [AppDeploymentFollowResponseState],
// [AppDeploymentFollowResponseStateUpdate], [shared.LogEvent].
// [AppDeploymentFollowResponseStateUpdate], [shared.LogEvent],
// [shared.HeartbeatEvent].
//
// Use the [AppDeploymentFollowResponseUnion.AsAny] method to switch on the
// variant.
//
// Use the methods beginning with 'As' to cast the union to one of its variants.
type AppDeploymentFollowResponseUnion struct {
// Any of "state", "state_update", "log".
// Any of "state", "state_update", "log", "sse_heartbeat".
Event string `json:"event"`
State string `json:"state"`
Timestamp time.Time `json:"timestamp"`
Expand Down Expand Up @@ -185,6 +186,7 @@ func (AppDeploymentFollowResponseStateUpdate) ImplAppDeploymentFollowResponseUni
// case kernel.AppDeploymentFollowResponseState:
// case kernel.AppDeploymentFollowResponseStateUpdate:
// case shared.LogEvent:
// case shared.HeartbeatEvent:
// default:
// fmt.Errorf("no variant present")
// }
Expand All @@ -196,6 +198,8 @@ func (u AppDeploymentFollowResponseUnion) AsAny() anyAppDeploymentFollowResponse
return u.AsStateUpdate()
case "log":
return u.AsLog()
case "sse_heartbeat":
return u.AsSseHeartbeat()
}
return nil
}
Expand All @@ -215,6 +219,11 @@ func (u AppDeploymentFollowResponseUnion) AsLog() (v shared.LogEvent) {
return
}

func (u AppDeploymentFollowResponseUnion) AsSseHeartbeat() (v shared.HeartbeatEvent) {
apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v)
return
}

// Returns the unmodified JSON received from the API
func (u AppDeploymentFollowResponseUnion) RawJSON() string { return u.JSON.raw }

Expand Down
7 changes: 6 additions & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,11 @@ func TestContextDeadlineStreaming(t *testing.T) {
},
}),
)
stream := client.Deployments.FollowStreaming(deadlineCtx, "id")
stream := client.Deployments.FollowStreaming(
deadlineCtx,
"id",
kernel.DeploymentFollowParams{},
)
for stream.Next() {
_ = stream.Current()
}
Expand Down Expand Up @@ -359,6 +363,7 @@ func TestContextDeadlineStreamingWithRequestTimeout(t *testing.T) {
stream := client.Deployments.FollowStreaming(
context.Background(),
"id",
kernel.DeploymentFollowParams{},
option.WithRequestTimeout((100 * time.Millisecond)),
)
for stream.Next() {
Expand Down
30 changes: 26 additions & 4 deletions deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import (
"io"
"mime/multipart"
"net/http"
"net/url"
"time"

"github.com/onkernel/kernel-go-sdk/internal/apiform"
"github.com/onkernel/kernel-go-sdk/internal/apijson"
"github.com/onkernel/kernel-go-sdk/internal/apiquery"
"github.com/onkernel/kernel-go-sdk/internal/requestconfig"
"github.com/onkernel/kernel-go-sdk/option"
"github.com/onkernel/kernel-go-sdk/packages/param"
Expand Down Expand Up @@ -66,7 +68,7 @@ func (r *DeploymentService) Get(ctx context.Context, id string, opts ...option.R
// Establishes a Server-Sent Events (SSE) stream that delivers real-time logs and
// status updates for a deployment. The stream terminates automatically once the
// deployment reaches a terminal state.
func (r *DeploymentService) FollowStreaming(ctx context.Context, id string, opts ...option.RequestOption) (stream *ssestream.Stream[DeploymentFollowResponseUnion]) {
func (r *DeploymentService) FollowStreaming(ctx context.Context, id string, query DeploymentFollowParams, opts ...option.RequestOption) (stream *ssestream.Stream[DeploymentFollowResponseUnion]) {
var (
raw *http.Response
err error
Expand All @@ -78,7 +80,7 @@ func (r *DeploymentService) FollowStreaming(ctx context.Context, id string, opts
return
}
path := fmt.Sprintf("deployments/%s/events", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &raw, opts...)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &raw, opts...)
return ssestream.NewStream[DeploymentFollowResponseUnion](ssestream.NewDecoder(raw), err)
}

Expand Down Expand Up @@ -253,13 +255,14 @@ const (

// DeploymentFollowResponseUnion contains all possible properties and values from
// [shared.LogEvent], [DeploymentStateEvent],
// [DeploymentFollowResponseAppVersionSummaryEvent], [shared.ErrorEvent].
// [DeploymentFollowResponseAppVersionSummaryEvent], [shared.ErrorEvent],
// [shared.HeartbeatEvent].
//
// Use the [DeploymentFollowResponseUnion.AsAny] method to switch on the variant.
//
// Use the methods beginning with 'As' to cast the union to one of its variants.
type DeploymentFollowResponseUnion struct {
// Any of "log", "deployment_state", nil, nil.
// Any of "log", "deployment_state", nil, nil, "sse_heartbeat".
Event string `json:"event"`
// This field is from variant [shared.LogEvent].
Message string `json:"message"`
Expand Down Expand Up @@ -316,6 +319,11 @@ func (u DeploymentFollowResponseUnion) AsErrorEvent() (v shared.ErrorEvent) {
return
}

func (u DeploymentFollowResponseUnion) AsSseHeartbeat() (v shared.HeartbeatEvent) {
apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v)
return
}

// Returns the unmodified JSON received from the API
func (u DeploymentFollowResponseUnion) RawJSON() string { return u.JSON.raw }

Expand Down Expand Up @@ -423,3 +431,17 @@ type DeploymentNewParamsRegion string
const (
DeploymentNewParamsRegionAwsUsEast1a DeploymentNewParamsRegion = "aws.us-east-1a"
)

type DeploymentFollowParams struct {
// Show logs since the given time (RFC timestamps or durations like 5m).
Since param.Opt[string] `query:"since,omitzero" json:"-"`
paramObj
}

// URLQuery serializes [DeploymentFollowParams]'s query parameters as `url.Values`.
func (r DeploymentFollowParams) URLQuery() (v url.Values, err error) {
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
ArrayFormat: apiquery.ArrayQueryFormatComma,
NestedFormat: apiquery.NestedQueryFormatBrackets,
})
}
13 changes: 3 additions & 10 deletions internal/encoding/json/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ type mapEncoder struct {
}

func (me mapEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.IsNil() {
if v.IsNil() /* EDIT(begin) */ || sentinel.IsValueNull(v) /* EDIT(end) */ {
e.WriteString("null")
return
}
Expand Down Expand Up @@ -855,7 +855,7 @@ type sliceEncoder struct {
}

func (se sliceEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
if v.IsNil() {
if v.IsNil() /* EDIT(begin) */ || sentinel.IsValueNull(v) /* EDIT(end) */ {
e.WriteString("null")
return
}
Expand Down Expand Up @@ -916,14 +916,7 @@ type ptrEncoder struct {
}

func (pe ptrEncoder) encode(e *encodeState, v reflect.Value, opts encOpts) {
// EDIT(begin)
//
// if v.IsNil() {
// e.WriteString("null")
// return
// }

if v.IsNil() || sentinel.IsValueNullPtr(v) || sentinel.IsValueNullSlice(v) {
if v.IsNil() {
e.WriteString("null")
return
}
Expand Down
61 changes: 25 additions & 36 deletions internal/encoding/json/sentinel/null.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,41 @@ import (
"sync"
)

var nullPtrsCache sync.Map // map[reflect.Type]*T

func NullPtr[T any]() *T {
t := shims.TypeFor[T]()
ptr, loaded := nullPtrsCache.Load(t) // avoid premature allocation
if !loaded {
ptr, _ = nullPtrsCache.LoadOrStore(t, new(T))
}
return (ptr.(*T))
type cacheEntry struct {
x any
ptr uintptr
kind reflect.Kind
}

var nullSlicesCache sync.Map // map[reflect.Type][]T
var nullCache sync.Map // map[reflect.Type]cacheEntry

func NullSlice[T any]() []T {
func NewNullSentinel[T any](mk func() T) T {
t := shims.TypeFor[T]()
slice, loaded := nullSlicesCache.Load(t) // avoid premature allocation
entry, loaded := nullCache.Load(t) // avoid premature allocation
if !loaded {
slice, _ = nullSlicesCache.LoadOrStore(t, []T{})
x := mk()
ptr := reflect.ValueOf(x).Pointer()
entry, _ = nullCache.LoadOrStore(t, cacheEntry{x, ptr, t.Kind()})
}
return slice.([]T)
return entry.(cacheEntry).x.(T)
}

func IsNullPtr[T any](ptr *T) bool {
nullptr, ok := nullPtrsCache.Load(shims.TypeFor[T]())
return ok && ptr == nullptr.(*T)
}

func IsNullSlice[T any](slice []T) bool {
nullSlice, ok := nullSlicesCache.Load(shims.TypeFor[T]())
return ok && reflect.ValueOf(slice).Pointer() == reflect.ValueOf(nullSlice).Pointer()
}

// internal only
func IsValueNullPtr(v reflect.Value) bool {
if v.Kind() != reflect.Ptr {
return false
// for internal use only
func IsValueNull(v reflect.Value) bool {
switch v.Kind() {
case reflect.Map, reflect.Slice:
null, ok := nullCache.Load(v.Type())
return ok && v.Pointer() == null.(cacheEntry).ptr
}
nullptr, ok := nullPtrsCache.Load(v.Type().Elem())
return ok && v.Pointer() == reflect.ValueOf(nullptr).Pointer()
return false
}

// internal only
func IsValueNullSlice(v reflect.Value) bool {
if v.Kind() != reflect.Slice {
return false
func IsNull[T any](v T) bool {
t := shims.TypeFor[T]()
switch t.Kind() {
case reflect.Map, reflect.Slice:
null, ok := nullCache.Load(t)
return ok && reflect.ValueOf(v).Pointer() == null.(cacheEntry).ptr
}
nullSlice, ok := nullSlicesCache.Load(v.Type().Elem())
return ok && v.Pointer() == reflect.ValueOf(nullSlice).Pointer()
return false
}
Loading