mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: record created_at/completed_at on reasoning ChatMessageParts (#24789)
Records reasoning start and end times on persisted reasoning `ChatMessagePart`s so reasoning duration can be computed for stored chats. Backend-only: no SSE changes and no frontend rendering ship in this PR. The `created_at` field on `ChatMessagePart` is extended to also be present on `reasoning` parts (it previously appeared only on `tool-call` and `tool-result`), and a new `completed_at` field is added for `reasoning` parts. ### How timestamps are recorded - `StreamPartTypeReasoningStart`: stamp `startedAt = dbtime.Now()` on the active reasoning state. - `StreamPartTypeReasoningEnd`: stamp `completedAt = dbtime.Now()` and append both into parallel `[]time.Time` slices on `stepResult`. - Persistence reads the slices in occurrence order (reasoning has no provider-side ID) and applies them to the matching `ChatMessagePart` via `buildAssistantPartsForPersist`. The first reasoning block's stamps go onto the first reasoning part, and so on. - `flushActiveState` flushes partial reasoning interrupted before `StreamPartTypeReasoningEnd` with `startedAt` from the active state and `completedAt = dbtime.Now()` at the interruption. ### Why two fields, not one? Tool calls and results are point events. The frontend computes their duration by subtracting the call's `created_at` from the result's `created_at`. Reasoning is one assistant part that brackets a span, so we record both endpoints on the part itself. ### Why not stamp in `PartFromContent`? Same rationale as #24101: `PartFromContent` is called during both SSE publishing and persistence. Stamping there would yield incorrect persistence-time timestamps for reasoning blocks that finished much earlier in the step. Instead we capture in the chatloop and apply during persistence. <details><summary>Implementation plan</summary> - `codersdk/chats.go`: extend `CreatedAt`'s `variants` to include `reasoning?`; add `CompletedAt *time.Time` with `variants:"reasoning?"`. - `coderd/x/chatd/chatloop/chatloop.go`: extend `reasoningState` with `startedAt`; extend `stepResult` and `PersistedStep` with parallel `[]time.Time` reasoning slices; stamp on `ReasoningStart`/`ReasoningEnd`; thread the slices through all `PersistStep` call sites including the interrupt-safe path; record partial reasoning in `flushActiveState`. - `coderd/x/chatd/attachments.go`: walk reasoning parts in occurrence order and apply `step.ReasoningStartedAt[i]` to `part.CreatedAt` and `step.ReasoningCompletedAt[i]` to `part.CompletedAt`. ### Tests - `codersdk/chats_test.go` round-trips `created_at` + `completed_at` on reasoning parts and verifies omission when absent and partial interrupted parts. - `coderd/x/chatd/chatprompt/chatprompt_test.go` asserts `PartFromContent(ReasoningContent{})` does NOT stamp timestamps. - `coderd/x/chatd/chatloop/chatloop_test.go` `TestRun_ReasoningTimestamps` drives a stream with two reasoning blocks and verifies parallel slices, monotonicity, ordering, non-zero values, and content-block ordering. `TestRun_InterruptedReasoningFlushesTimestamps` cancels mid-reasoning and verifies `flushActiveState` records a non-zero pair. - `coderd/x/chatd/attachments_test.go` covers `buildAssistantPartsForPersist` for normal interleaved reasoning, partial (zero `completed_at`), and missing slices. </details> > Generated by Coder Agents. Co-authored-by: Coder Agent <agent@coder.com>
This commit is contained in:
Generated
+6
-1
@@ -16045,6 +16045,11 @@ const docTemplate = `{
|
||||
"args_delta": {
|
||||
"type": "string"
|
||||
},
|
||||
"completed_at": {
|
||||
"description": "CompletedAt is the time a reasoning part finished streaming,\nso reasoning duration can be computed as completed_at minus\ncreated_at. For interrupted reasoning, this is the\ninterruption time. Absent when reasoning timestamp data was\nnot recorded (e.g. messages persisted before this feature\nwas added).",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"content": {
|
||||
"description": "The code content from the diff that was commented on.",
|
||||
"type": "string"
|
||||
@@ -16083,7 +16088,7 @@ const docTemplate = `{
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "CreatedAt records when this part was produced. Present on\ntool-call and tool-result parts so the frontend can compute\ntool execution duration.",
|
||||
"description": "CreatedAt is the timestamp this part carries. The semantics\ndepend on the part type: for tool-call and tool-result parts\nit is the time the call was emitted or the result was\nproduced (tool duration is the result's created_at minus the\ncall's created_at); for reasoning parts it is the time\nreasoning started streaming.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
|
||||
Generated
+6
-1
@@ -14447,6 +14447,11 @@
|
||||
"args_delta": {
|
||||
"type": "string"
|
||||
},
|
||||
"completed_at": {
|
||||
"description": "CompletedAt is the time a reasoning part finished streaming,\nso reasoning duration can be computed as completed_at minus\ncreated_at. For interrupted reasoning, this is the\ninterruption time. Absent when reasoning timestamp data was\nnot recorded (e.g. messages persisted before this feature\nwas added).",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"content": {
|
||||
"description": "The code content from the diff that was commented on.",
|
||||
"type": "string"
|
||||
@@ -14485,7 +14490,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"created_at": {
|
||||
"description": "CreatedAt records when this part was produced. Present on\ntool-call and tool-result parts so the frontend can compute\ntool execution duration.",
|
||||
"description": "CreatedAt is the timestamp this part carries. The semantics\ndepend on the part type: for tool-call and tool-result parts\nit is the time the call was emitted or the result was\nproduced (tool duration is the result's created_at minus the\ncall's created_at); for reasoning parts it is the time\nreasoning started streaming.",
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
|
||||
@@ -22,6 +22,11 @@ func buildAssistantPartsForPersist(
|
||||
toolNameToConfigID map[string]uuid.UUID,
|
||||
) []codersdk.ChatMessagePart {
|
||||
parts := make([]codersdk.ChatMessagePart, 0, len(assistantBlocks)+len(toolResults))
|
||||
// reasoningIdx walks reasoning blocks in occurrence order so we
|
||||
// can apply the matching ReasoningStartedAt/ReasoningCompletedAt
|
||||
// entry from step onto each reasoning part's CreatedAt and
|
||||
// CompletedAt.
|
||||
reasoningIdx := 0
|
||||
for _, block := range assistantBlocks {
|
||||
part := chatprompt.PartFromContentWithLogger(ctx, logger, block)
|
||||
if part.ToolName != "" {
|
||||
@@ -39,6 +44,19 @@ func buildAssistantPartsForPersist(
|
||||
part.CreatedAt = &ts
|
||||
}
|
||||
}
|
||||
if part.Type == codersdk.ChatMessagePartTypeReasoning {
|
||||
if reasoningIdx < len(step.ReasoningStartedAt) {
|
||||
if ts := step.ReasoningStartedAt[reasoningIdx]; !ts.IsZero() {
|
||||
part.CreatedAt = &ts
|
||||
}
|
||||
}
|
||||
if reasoningIdx < len(step.ReasoningCompletedAt) {
|
||||
if ts := step.ReasoningCompletedAt[reasoningIdx]; !ts.IsZero() {
|
||||
part.CompletedAt = &ts
|
||||
}
|
||||
}
|
||||
reasoningIdx++
|
||||
}
|
||||
parts = append(parts, part)
|
||||
}
|
||||
for _, tr := range toolResults {
|
||||
|
||||
@@ -134,3 +134,114 @@ func TestBuildAssistantPartsForPersist_InvalidAttachmentMetadataSkipsOnlyBrokenR
|
||||
require.Equal(t, "image/png", parts[1].MediaType)
|
||||
require.Equal(t, "good.png", parts[1].Name)
|
||||
}
|
||||
|
||||
func TestBuildAssistantPartsForPersist_AppliesReasoningTimestamps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
startedAt1 := time.Date(2026, time.April, 10, 12, 0, 0, 0, time.UTC)
|
||||
completedAt1 := startedAt1.Add(500 * time.Millisecond)
|
||||
startedAt2 := completedAt1.Add(time.Second)
|
||||
completedAt2 := startedAt2.Add(750 * time.Millisecond)
|
||||
|
||||
// Interleave reasoning blocks with a text block to confirm the
|
||||
// index walks reasoning content in occurrence order without
|
||||
// being thrown off by non-reasoning entries.
|
||||
parts := buildAssistantPartsForPersist(
|
||||
context.Background(),
|
||||
testutil.Logger(t),
|
||||
[]fantasy.Content{
|
||||
fantasy.ReasoningContent{Text: "first thought"},
|
||||
fantasy.TextContent{Text: "intermission"},
|
||||
fantasy.ReasoningContent{Text: "second thought"},
|
||||
},
|
||||
nil,
|
||||
chatloop.PersistedStep{
|
||||
ReasoningStartedAt: []time.Time{startedAt1, startedAt2},
|
||||
ReasoningCompletedAt: []time.Time{completedAt1, completedAt2},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
require.Len(t, parts, 3)
|
||||
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeReasoning, parts[0].Type)
|
||||
require.Equal(t, "first thought", parts[0].Text)
|
||||
require.NotNil(t, parts[0].CreatedAt)
|
||||
require.True(t, parts[0].CreatedAt.Equal(startedAt1),
|
||||
"first reasoning part must use ReasoningStartedAt[0]")
|
||||
require.NotNil(t, parts[0].CompletedAt)
|
||||
require.True(t, parts[0].CompletedAt.Equal(completedAt1),
|
||||
"first reasoning part must use ReasoningCompletedAt[0]")
|
||||
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeText, parts[1].Type)
|
||||
require.Nil(t, parts[1].CreatedAt,
|
||||
"text part must not inherit reasoning timestamps")
|
||||
require.Nil(t, parts[1].CompletedAt)
|
||||
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeReasoning, parts[2].Type)
|
||||
require.Equal(t, "second thought", parts[2].Text)
|
||||
require.NotNil(t, parts[2].CreatedAt)
|
||||
require.True(t, parts[2].CreatedAt.Equal(startedAt2),
|
||||
"second reasoning part must use ReasoningStartedAt[1]")
|
||||
require.NotNil(t, parts[2].CompletedAt)
|
||||
require.True(t, parts[2].CompletedAt.Equal(completedAt2),
|
||||
"second reasoning part must use ReasoningCompletedAt[1]")
|
||||
}
|
||||
|
||||
func TestBuildAssistantPartsForPersist_PartialReasoningTimestamps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
startedAt := time.Date(2026, time.April, 10, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
// Tests the persistence helper when the parallel CompletedAt
|
||||
// slot is zero-valued, ensuring it leaves CompletedAt nil rather
|
||||
// than setting it to the Go zero time. No production code path
|
||||
// currently emits a zero CompletedAt alongside a non-zero
|
||||
// StartedAt (flushActiveState always stamps both with
|
||||
// dbtime.Now()), so this is a defensive boundary test for the
|
||||
// `variants:"reasoning?"` contract.
|
||||
parts := buildAssistantPartsForPersist(
|
||||
context.Background(),
|
||||
testutil.Logger(t),
|
||||
[]fantasy.Content{
|
||||
fantasy.ReasoningContent{Text: "incomplete thought"},
|
||||
},
|
||||
nil,
|
||||
chatloop.PersistedStep{
|
||||
ReasoningStartedAt: []time.Time{startedAt},
|
||||
ReasoningCompletedAt: []time.Time{{}},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
require.Len(t, parts, 1)
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeReasoning, parts[0].Type)
|
||||
require.NotNil(t, parts[0].CreatedAt)
|
||||
require.True(t, parts[0].CreatedAt.Equal(startedAt))
|
||||
require.Nil(t, parts[0].CompletedAt,
|
||||
"zero-valued ReasoningCompletedAt must not produce a stamp")
|
||||
}
|
||||
|
||||
func TestBuildAssistantPartsForPersist_MissingReasoningTimestamps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Legacy persisted steps and steps that never observed a
|
||||
// reasoning block carry empty timestamp slices. The helper must
|
||||
// leave CreatedAt and CompletedAt nil instead of panicking on
|
||||
// the out-of-range index.
|
||||
parts := buildAssistantPartsForPersist(
|
||||
context.Background(),
|
||||
testutil.Logger(t),
|
||||
[]fantasy.Content{
|
||||
fantasy.ReasoningContent{Text: "no timestamps recorded"},
|
||||
},
|
||||
nil,
|
||||
chatloop.PersistedStep{},
|
||||
nil,
|
||||
)
|
||||
|
||||
require.Len(t, parts, 1)
|
||||
require.Equal(t, codersdk.ChatMessagePartTypeReasoning, parts[0].Type)
|
||||
require.Nil(t, parts[0].CreatedAt)
|
||||
require.Nil(t, parts[0].CompletedAt)
|
||||
}
|
||||
|
||||
@@ -97,6 +97,15 @@ type PersistedStep struct {
|
||||
// Applied by the persistence layer to set CreatedAt
|
||||
// on persisted tool-result ChatMessageParts.
|
||||
ToolResultCreatedAt map[string]time.Time
|
||||
// ReasoningStartedAt and ReasoningCompletedAt are parallel
|
||||
// slices indexed by the occurrence order of reasoning
|
||||
// content in Content. The persistence layer walks reasoning
|
||||
// parts in order and applies these timestamps to the
|
||||
// corresponding ChatMessageParts so the frontend can render
|
||||
// reasoning duration. Reasoning parts have no provider-side
|
||||
// stable ID, so order is the only correlation we have.
|
||||
ReasoningStartedAt []time.Time
|
||||
ReasoningCompletedAt []time.Time
|
||||
}
|
||||
|
||||
// RunOptions configures a single streaming chat loop run.
|
||||
@@ -223,14 +232,16 @@ type ProviderTool struct {
|
||||
// step. Since we own the stream consumer, all content is tracked
|
||||
// directly here, no shadow draft state needed.
|
||||
type stepResult struct {
|
||||
content []fantasy.Content
|
||||
usage fantasy.Usage
|
||||
providerMetadata fantasy.ProviderMetadata
|
||||
finishReason fantasy.FinishReason
|
||||
toolCalls []fantasy.ToolCallContent
|
||||
shouldContinue bool
|
||||
toolCallCreatedAt map[string]time.Time
|
||||
toolResultCreatedAt map[string]time.Time
|
||||
content []fantasy.Content
|
||||
usage fantasy.Usage
|
||||
providerMetadata fantasy.ProviderMetadata
|
||||
finishReason fantasy.FinishReason
|
||||
toolCalls []fantasy.ToolCallContent
|
||||
shouldContinue bool
|
||||
toolCallCreatedAt map[string]time.Time
|
||||
toolResultCreatedAt map[string]time.Time
|
||||
reasoningStartedAt []time.Time
|
||||
reasoningCompletedAt []time.Time
|
||||
}
|
||||
|
||||
// toResponseMessages converts step content into messages suitable
|
||||
@@ -336,8 +347,9 @@ func (r stepResult) toResponseMessages() []fantasy.Message {
|
||||
// reasoningState accumulates reasoning content and provider
|
||||
// metadata while the stream is in flight.
|
||||
type reasoningState struct {
|
||||
text string
|
||||
options fantasy.ProviderMetadata
|
||||
text string
|
||||
options fantasy.ProviderMetadata
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
// Run executes the chat step-stream loop and delegates
|
||||
@@ -575,13 +587,15 @@ func Run(ctx context.Context, opts RunOptions) error {
|
||||
// check and here, fall back to the interrupt-safe
|
||||
// path so partial content is not lost.
|
||||
if err := opts.PersistStep(ctx, PersistedStep{
|
||||
Content: result.content,
|
||||
Usage: result.usage,
|
||||
ContextLimit: contextLimit,
|
||||
ProviderResponseID: chatopenai.ExtractResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
Runtime: time.Since(stepStart),
|
||||
ToolCallCreatedAt: result.toolCallCreatedAt,
|
||||
ToolResultCreatedAt: result.toolResultCreatedAt,
|
||||
Content: result.content,
|
||||
Usage: result.usage,
|
||||
ContextLimit: contextLimit,
|
||||
ProviderResponseID: chatopenai.ExtractResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
Runtime: time.Since(stepStart),
|
||||
ToolCallCreatedAt: result.toolCallCreatedAt,
|
||||
ToolResultCreatedAt: result.toolResultCreatedAt,
|
||||
ReasoningStartedAt: result.reasoningStartedAt,
|
||||
ReasoningCompletedAt: result.reasoningCompletedAt,
|
||||
}); err != nil {
|
||||
if errors.Is(err, ErrInterrupted) {
|
||||
persistInterruptedStep(ctx, opts, &result)
|
||||
@@ -915,8 +929,9 @@ func processStepStream(
|
||||
|
||||
case fantasy.StreamPartTypeReasoningStart:
|
||||
activeReasoningContent[part.ID] = reasoningState{
|
||||
text: part.Delta,
|
||||
options: part.ProviderMetadata,
|
||||
text: part.Delta,
|
||||
options: part.ProviderMetadata,
|
||||
startedAt: dbtime.Now(),
|
||||
}
|
||||
|
||||
case fantasy.StreamPartTypeReasoningDelta:
|
||||
@@ -939,6 +954,8 @@ func processStepStream(
|
||||
ProviderMetadata: active.options,
|
||||
}
|
||||
result.content = append(result.content, content)
|
||||
result.reasoningStartedAt = append(result.reasoningStartedAt, active.startedAt)
|
||||
result.reasoningCompletedAt = append(result.reasoningCompletedAt, dbtime.Now())
|
||||
delete(activeReasoningContent, part.ID)
|
||||
}
|
||||
case fantasy.StreamPartTypeToolInputStart:
|
||||
@@ -1376,6 +1393,8 @@ func persistPendingDynamicStep(
|
||||
ProviderResponseID: chatopenai.ExtractResponseIDIfStored(opts.ProviderOptions, result.providerMetadata),
|
||||
Runtime: time.Since(stepStart),
|
||||
PendingDynamicToolCalls: pending,
|
||||
ReasoningStartedAt: result.reasoningStartedAt,
|
||||
ReasoningCompletedAt: result.reasoningCompletedAt,
|
||||
}); err != nil {
|
||||
if errors.Is(err, ErrInterrupted) {
|
||||
persistInterruptedStep(ctx, opts, result)
|
||||
@@ -1605,7 +1624,11 @@ func flushActiveState(
|
||||
}
|
||||
}
|
||||
|
||||
// Flush partial reasoning content.
|
||||
// Flush partial reasoning content. The matching
|
||||
// completedAt is filled in here with the interruption
|
||||
// time so partial reasoning shows the time spent before
|
||||
// the interruption.
|
||||
flushedAt := dbtime.Now()
|
||||
for _, rs := range activeReasoning {
|
||||
if rs.text == "" && !chatsanitize.HasAnthropicSignedReasoningOptions(fantasy.ProviderOptions(rs.options)) {
|
||||
continue
|
||||
@@ -1614,6 +1637,8 @@ func flushActiveState(
|
||||
Text: rs.text,
|
||||
ProviderMetadata: rs.options,
|
||||
})
|
||||
result.reasoningStartedAt = append(result.reasoningStartedAt, rs.startedAt)
|
||||
result.reasoningCompletedAt = append(result.reasoningCompletedAt, flushedAt)
|
||||
}
|
||||
|
||||
// Flush in-progress tool calls. These haven't received a
|
||||
@@ -1727,9 +1752,11 @@ func persistInterruptedStep(
|
||||
|
||||
persistCtx := context.WithoutCancel(ctx)
|
||||
if err := opts.PersistStep(persistCtx, PersistedStep{
|
||||
Content: content,
|
||||
ToolCallCreatedAt: toolCallCreatedAt,
|
||||
ToolResultCreatedAt: toolResultCreatedAt,
|
||||
Content: content,
|
||||
ToolCallCreatedAt: toolCallCreatedAt,
|
||||
ToolResultCreatedAt: toolResultCreatedAt,
|
||||
ReasoningStartedAt: result.reasoningStartedAt,
|
||||
ReasoningCompletedAt: result.reasoningCompletedAt,
|
||||
}); err != nil {
|
||||
if opts.OnInterruptedPersistError != nil {
|
||||
opts.OnInterruptedPersistError(err)
|
||||
|
||||
@@ -4415,3 +4415,170 @@ func TestExecuteSingleTool_MediaBase64Encoding(t *testing.T) {
|
||||
require.Contains(t, textOutput.Text, "world")
|
||||
})
|
||||
}
|
||||
|
||||
// TestRun_ReasoningTimestamps verifies that StreamPartTypeReasoningStart
|
||||
// and StreamPartTypeReasoningEnd produce parallel ReasoningStartedAt /
|
||||
// ReasoningCompletedAt slices on PersistedStep, in the same occurrence
|
||||
// order as the reasoning content blocks. The frontend computes
|
||||
// reasoning duration as completed_at - started_at.
|
||||
func TestRun_ReasoningTimestamps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := &chattest.FakeModel{
|
||||
ProviderName: "fake",
|
||||
StreamFn: func(_ context.Context, _ fantasy.Call) (fantasy.StreamResponse, error) {
|
||||
return streamFromParts([]fantasy.StreamPart{
|
||||
{Type: fantasy.StreamPartTypeReasoningStart, ID: "reason-1"},
|
||||
{Type: fantasy.StreamPartTypeReasoningDelta, ID: "reason-1", Delta: "first thought"},
|
||||
{Type: fantasy.StreamPartTypeReasoningEnd, ID: "reason-1"},
|
||||
{Type: fantasy.StreamPartTypeReasoningStart, ID: "reason-2"},
|
||||
{Type: fantasy.StreamPartTypeReasoningDelta, ID: "reason-2", Delta: "second thought"},
|
||||
{Type: fantasy.StreamPartTypeReasoningEnd, ID: "reason-2"},
|
||||
{Type: fantasy.StreamPartTypeTextStart, ID: "text-1"},
|
||||
{Type: fantasy.StreamPartTypeTextDelta, ID: "text-1", Delta: "answer"},
|
||||
{Type: fantasy.StreamPartTypeTextEnd, ID: "text-1"},
|
||||
{Type: fantasy.StreamPartTypeFinish, FinishReason: fantasy.FinishReasonStop},
|
||||
}), nil
|
||||
},
|
||||
}
|
||||
|
||||
var persistedSteps []PersistedStep
|
||||
err := Run(context.Background(), RunOptions{
|
||||
Model: model,
|
||||
Messages: []fantasy.Message{
|
||||
textMessage(fantasy.MessageRoleUser, "think"),
|
||||
},
|
||||
MaxSteps: 1,
|
||||
PersistStep: func(_ context.Context, step PersistedStep) error {
|
||||
persistedSteps = append(persistedSteps, step)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, persistedSteps, 1)
|
||||
|
||||
step := persistedSteps[0]
|
||||
|
||||
// Both reasoning blocks must produce parallel timestamp entries.
|
||||
require.Len(t, step.ReasoningStartedAt, 2,
|
||||
"each StreamPartTypeReasoningEnd must record a started_at")
|
||||
require.Len(t, step.ReasoningCompletedAt, 2,
|
||||
"each StreamPartTypeReasoningEnd must record a completed_at")
|
||||
|
||||
// Timestamps must be monotonic per block (completed_at >= started_at),
|
||||
// and both timestamps must be populated. Asserting only monotonicity
|
||||
// is not enough: time.Time{} is year 0001, so completed_at.Before(zero)
|
||||
// is trivially false and a regression that drops the started_at stamp
|
||||
// would slip past the comparison.
|
||||
for i := range step.ReasoningStartedAt {
|
||||
require.False(t, step.ReasoningStartedAt[i].IsZero(),
|
||||
"started_at[%d] must be non-zero", i)
|
||||
require.False(t, step.ReasoningCompletedAt[i].IsZero(),
|
||||
"completed_at[%d] must be non-zero", i)
|
||||
require.False(t,
|
||||
step.ReasoningCompletedAt[i].Before(step.ReasoningStartedAt[i]),
|
||||
"completed_at[%d] must be >= started_at[%d]", i, i)
|
||||
}
|
||||
|
||||
// Successive blocks must be ordered: reasoning-2 cannot start
|
||||
// before reasoning-1 completes.
|
||||
require.False(t,
|
||||
step.ReasoningStartedAt[1].Before(step.ReasoningCompletedAt[0]),
|
||||
"reasoning-2 started_at must be >= reasoning-1 completed_at")
|
||||
|
||||
// The reasoning content blocks must appear in the same order
|
||||
// in step.Content so the persistence layer can correlate by
|
||||
// occurrence order.
|
||||
var reasoningOrder []string
|
||||
for _, c := range step.Content {
|
||||
if r, ok := fantasy.AsContentType[fantasy.ReasoningContent](c); ok {
|
||||
reasoningOrder = append(reasoningOrder, r.Text)
|
||||
}
|
||||
}
|
||||
require.Equal(t, []string{"first thought", "second thought"}, reasoningOrder)
|
||||
}
|
||||
|
||||
func TestRun_InterruptedReasoningFlushesTimestamps(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
started := make(chan struct{})
|
||||
model := &chattest.FakeModel{
|
||||
ProviderName: "fake",
|
||||
StreamFn: func(ctx context.Context, _ fantasy.Call) (fantasy.StreamResponse, error) {
|
||||
return iter.Seq[fantasy.StreamPart](func(yield func(fantasy.StreamPart) bool) {
|
||||
parts := []fantasy.StreamPart{
|
||||
{Type: fantasy.StreamPartTypeReasoningStart, ID: "reason-1"},
|
||||
{Type: fantasy.StreamPartTypeReasoningDelta, ID: "reason-1", Delta: "interrupted thought"},
|
||||
}
|
||||
for _, part := range parts {
|
||||
if !yield(part) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-started:
|
||||
default:
|
||||
close(started)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
_ = yield(fantasy.StreamPart{
|
||||
Type: fantasy.StreamPartTypeError,
|
||||
Error: ctx.Err(),
|
||||
})
|
||||
}), nil
|
||||
},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
defer cancel(nil)
|
||||
|
||||
go func() {
|
||||
<-started
|
||||
cancel(ErrInterrupted)
|
||||
}()
|
||||
|
||||
var persistedStep PersistedStep
|
||||
err := Run(ctx, RunOptions{
|
||||
Model: model,
|
||||
Messages: []fantasy.Message{
|
||||
textMessage(fantasy.MessageRoleUser, "think"),
|
||||
},
|
||||
MaxSteps: 1,
|
||||
PersistStep: func(_ context.Context, step PersistedStep) error {
|
||||
persistedStep = step
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.ErrorIs(t, err, ErrInterrupted)
|
||||
|
||||
// flushActiveState must have appended exactly one entry to each
|
||||
// parallel slice, matching the single in-progress reasoning block.
|
||||
require.Len(t, persistedStep.ReasoningStartedAt, 1,
|
||||
"interrupted reasoning must flush its started_at")
|
||||
require.Len(t, persistedStep.ReasoningCompletedAt, 1,
|
||||
"interrupted reasoning must flush a completed_at stamp")
|
||||
|
||||
// Both timestamps must be populated and the completed stamp
|
||||
// must be at or after the started stamp.
|
||||
require.False(t, persistedStep.ReasoningStartedAt[0].IsZero(),
|
||||
"flushed reasoning started_at must be non-zero")
|
||||
require.False(t, persistedStep.ReasoningCompletedAt[0].IsZero(),
|
||||
"flushed reasoning completed_at must be non-zero")
|
||||
require.False(t,
|
||||
persistedStep.ReasoningCompletedAt[0].Before(persistedStep.ReasoningStartedAt[0]),
|
||||
"flushed completed_at must be >= started_at")
|
||||
|
||||
// The flushed reasoning content must appear in step.Content so
|
||||
// the persistence layer's occurrence-order correlation lines up
|
||||
// with the timestamp slices.
|
||||
var reasoningBlocks []fantasy.ReasoningContent
|
||||
for _, c := range persistedStep.Content {
|
||||
if r, ok := fantasy.AsContentType[fantasy.ReasoningContent](c); ok {
|
||||
reasoningBlocks = append(reasoningBlocks, r)
|
||||
}
|
||||
}
|
||||
require.Len(t, reasoningBlocks, 1)
|
||||
require.Equal(t, "interrupted thought", reasoningBlocks[0].Text)
|
||||
}
|
||||
|
||||
@@ -3008,6 +3008,22 @@ func TestPartFromContent_CreatedAtNotStamped(t *testing.T) {
|
||||
part := chatprompt.PartFromContent(fantasy.TextContent{Text: "hello"})
|
||||
assert.Nil(t, part.CreatedAt)
|
||||
})
|
||||
|
||||
t.Run("ReasoningHasNilCreatedAndCompletedAt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Same rationale as ToolCall: the chatloop layer records
|
||||
// reasoning timestamps separately and the persistence
|
||||
// layer applies them. PartFromContent is called in
|
||||
// multiple contexts so stamping here would yield
|
||||
// incorrect durations.
|
||||
part := chatprompt.PartFromContent(fantasy.ReasoningContent{Text: "thinking"})
|
||||
assert.Nil(t, part.CreatedAt)
|
||||
assert.Nil(t, part.CompletedAt)
|
||||
|
||||
partPtr := chatprompt.PartFromContent(&fantasy.ReasoningContent{Text: "thinking"})
|
||||
assert.Nil(t, partPtr.CreatedAt)
|
||||
assert.Nil(t, partPtr.CompletedAt)
|
||||
})
|
||||
}
|
||||
|
||||
func TestToolResultAntivenom(t *testing.T) {
|
||||
|
||||
+14
-4
@@ -278,10 +278,20 @@ type ChatMessagePart struct {
|
||||
// ProviderExecuted indicates the tool call was executed by
|
||||
// the provider (e.g. Anthropic computer use).
|
||||
ProviderExecuted bool `json:"provider_executed,omitempty" variants:"tool-call?,tool-result?"`
|
||||
// CreatedAt records when this part was produced. Present on
|
||||
// tool-call and tool-result parts so the frontend can compute
|
||||
// tool execution duration.
|
||||
CreatedAt *time.Time `json:"created_at,omitempty" format:"date-time" variants:"tool-call?,tool-result?"`
|
||||
// CreatedAt is the timestamp this part carries. The semantics
|
||||
// depend on the part type: for tool-call and tool-result parts
|
||||
// it is the time the call was emitted or the result was
|
||||
// produced (tool duration is the result's created_at minus the
|
||||
// call's created_at); for reasoning parts it is the time
|
||||
// reasoning started streaming.
|
||||
CreatedAt *time.Time `json:"created_at,omitempty" format:"date-time" variants:"tool-call?,tool-result?,reasoning?"`
|
||||
// CompletedAt is the time a reasoning part finished streaming,
|
||||
// so reasoning duration can be computed as completed_at minus
|
||||
// created_at. For interrupted reasoning, this is the
|
||||
// interruption time. Absent when reasoning timestamp data was
|
||||
// not recorded (e.g. messages persisted before this feature
|
||||
// was added).
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty" format:"date-time" variants:"reasoning?"`
|
||||
// ContextFilePath is the absolute path of a file loaded into
|
||||
// the LLM context (e.g. an AGENTS.md instruction file).
|
||||
ContextFilePath string `json:"context_file_path" variants:"context-file"`
|
||||
|
||||
@@ -393,6 +393,70 @@ func TestChatMessagePart_CreatedAt_JSON(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestChatMessagePart_ReasoningTimestamps_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("RoundTrips", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
startedAt := time.Date(2025, 6, 15, 12, 30, 0, 0, time.UTC)
|
||||
completedAt := startedAt.Add(2 * time.Second)
|
||||
part := codersdk.ChatMessagePart{
|
||||
Type: codersdk.ChatMessagePartTypeReasoning,
|
||||
Text: "thinking out loud",
|
||||
CreatedAt: &startedAt,
|
||||
CompletedAt: &completedAt,
|
||||
}
|
||||
data, err := json.Marshal(part)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(data), `"created_at"`)
|
||||
require.Contains(t, string(data), `"completed_at"`)
|
||||
|
||||
var decoded codersdk.ChatMessagePart
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decoded.CreatedAt)
|
||||
require.NotNil(t, decoded.CompletedAt)
|
||||
require.True(t, startedAt.Equal(*decoded.CreatedAt))
|
||||
require.True(t, completedAt.Equal(*decoded.CompletedAt))
|
||||
})
|
||||
|
||||
t.Run("OmittedWhenNil", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
part := codersdk.ChatMessagePart{
|
||||
Type: codersdk.ChatMessagePartTypeReasoning,
|
||||
Text: "thinking out loud",
|
||||
}
|
||||
data, err := json.Marshal(part)
|
||||
require.NoError(t, err)
|
||||
require.NotContains(t, string(data), `"created_at"`)
|
||||
require.NotContains(t, string(data), `"completed_at"`)
|
||||
})
|
||||
|
||||
t.Run("LegacyCreatedAtWithoutCompletedAt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// CompletedAt is omitted on messages persisted before this
|
||||
// feature shipped. Confirm round-trip leaves CompletedAt nil
|
||||
// while preserving CreatedAt so legacy data does not break
|
||||
// API consumers.
|
||||
startedAt := time.Date(2025, 6, 15, 12, 30, 0, 0, time.UTC)
|
||||
part := codersdk.ChatMessagePart{
|
||||
Type: codersdk.ChatMessagePartTypeReasoning,
|
||||
Text: "legacy reasoning",
|
||||
CreatedAt: &startedAt,
|
||||
}
|
||||
data, err := json.Marshal(part)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(data), `"created_at"`)
|
||||
require.NotContains(t, string(data), `"completed_at"`)
|
||||
|
||||
var decoded codersdk.ChatMessagePart
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decoded.CreatedAt)
|
||||
require.Nil(t, decoded.CompletedAt)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModelCostConfig_LegacyNumericJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Generated
+124
-105
@@ -88,6 +88,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -172,111 +173,112 @@ Experimental: this endpoint is subject to change.
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-----------------------------------|------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» agent_id` | string(uuid) | false | | |
|
||||
| `» archived` | boolean | false | | |
|
||||
| `» build_id` | string(uuid) | false | | |
|
||||
| `» children` | [codersdk.Chat](schemas.md#codersdkchat) | false | | Children holds child (subagent) chats nested under this root chat. Always initialized to an empty slice so the JSON field is present as []. Child chats cannot create their own subagents, so nesting depth is capped at 1 and this slice is always empty for child chats. |
|
||||
| `» client_type` | [codersdk.ChatClientType](schemas.md#codersdkchatclienttype) | false | | |
|
||||
| `» created_at` | string(date-time) | false | | |
|
||||
| `» diff_status` | [codersdk.ChatDiffStatus](schemas.md#codersdkchatdiffstatus) | false | | |
|
||||
| `»» additions` | integer | false | | |
|
||||
| `»» approved` | boolean | false | | |
|
||||
| `»» author_avatar_url` | string | false | | |
|
||||
| `»» author_login` | string | false | | |
|
||||
| `»» base_branch` | string | false | | |
|
||||
| `»» changed_files` | integer | false | | |
|
||||
| `»» changes_requested` | boolean | false | | |
|
||||
| `»» chat_id` | string(uuid) | false | | |
|
||||
| `»» commits` | integer | false | | |
|
||||
| `»» deletions` | integer | false | | |
|
||||
| `»» head_branch` | string | false | | |
|
||||
| `»» pr_number` | integer | false | | |
|
||||
| `»» pull_request_draft` | boolean | false | | |
|
||||
| `»» pull_request_state` | string | false | | |
|
||||
| `»» pull_request_title` | string | false | | |
|
||||
| `»» refreshed_at` | string(date-time) | false | | |
|
||||
| `»» reviewer_count` | integer | false | | |
|
||||
| `»» stale_at` | string(date-time) | false | | |
|
||||
| `»» url` | string | false | | |
|
||||
| `» files` | array | false | | |
|
||||
| `»» created_at` | string(date-time) | false | | |
|
||||
| `»» id` | string(uuid) | false | | |
|
||||
| `»» mime_type` | string | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» organization_id` | string(uuid) | false | | |
|
||||
| `»» owner_id` | string(uuid) | false | | |
|
||||
| `» has_unread` | boolean | false | | Has unread is true when assistant messages exist beyond the owner's read cursor, which updates on stream connect and disconnect. |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
| `» labels` | object | false | | |
|
||||
| `»» [any property]` | string | false | | |
|
||||
| `» last_error` | [codersdk.ChatError](schemas.md#codersdkchaterror) | false | | |
|
||||
| `»» detail` | string | false | | Detail is optional provider-specific context shown alongside the normalized error message when available. |
|
||||
| `»» kind` | [codersdk.ChatErrorKind](schemas.md#codersdkchaterrorkind) | false | | Kind classifies the error for consistent client rendering. |
|
||||
| `»» message` | string | false | | Message is the normalized, user-facing error message. |
|
||||
| `»» provider` | string | false | | Provider identifies the upstream model provider when known. |
|
||||
| `»» retryable` | boolean | false | | Retryable reports whether the underlying error is transient. |
|
||||
| `»» status_code` | integer | false | | Status code is the best-effort upstream HTTP status code. |
|
||||
| `» last_injected_context` | array | false | | Last injected context holds the most recently persisted injected context parts (AGENTS.md files and skills). It is updated only when context changes, on first workspace attach or agent change. |
|
||||
| `»» args` | array | false | | |
|
||||
| `»» args_delta` | string | false | | |
|
||||
| `»» content` | string | false | | The code content from the diff that was commented on. |
|
||||
| `»» context_file_agent_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | Context file agent ID is the workspace agent that provided this context file. Used to detect when the agent changes (e.g. workspace rebuilt) so instruction files can be re-persisted with fresh content. |
|
||||
| `»»» uuid` | string | false | | |
|
||||
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
|
||||
| `»» context_file_content` | string | false | | Context file content holds the file content sent to the LLM. Internal only: stripped before API responses to keep payloads small. The backend reads it when building the prompt via partsToMessageParts. |
|
||||
| `»» context_file_directory` | string | false | | Context file directory is the working directory of the workspace agent. Internal only: same purpose as ContextFileOS. |
|
||||
| `»» context_file_os` | string | false | | Context file os is the operating system of the workspace agent. Internal only: used during prompt expansion so the LLM knows the OS even on turns where InsertSystem is not called. |
|
||||
| `»» context_file_path` | string | false | | Context file path is the absolute path of a file loaded into the LLM context (e.g. an AGENTS.md instruction file). |
|
||||
| `»» context_file_skill_meta_file` | string | false | | Context file skill meta file is the basename of the skill meta file (e.g. "SKILL.md") at the time of persistence. Internal only: restored on subsequent turns so the read_skill tool uses the correct filename even when the agent configured a non-default value. |
|
||||
| `»» context_file_truncated` | boolean | false | | Context file truncated indicates the file exceeded the 64KiB instruction file limit and was truncated. |
|
||||
| `»» created_at` | string(date-time) | false | | Created at records when this part was produced. Present on tool-call and tool-result parts so the frontend can compute tool execution duration. |
|
||||
| `»» data` | array | false | | |
|
||||
| `»» end_line` | integer | false | | |
|
||||
| `»» file_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | |
|
||||
| `»»» uuid` | string | false | | |
|
||||
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
|
||||
| `»» file_name` | string | false | | |
|
||||
| `»» is_error` | boolean | false | | |
|
||||
| `»» is_media` | boolean | false | | |
|
||||
| `»» mcp_server_config_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | |
|
||||
| `»»» uuid` | string | false | | |
|
||||
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
|
||||
| `»» media_type` | string | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» provider_executed` | boolean | false | | Provider executed indicates the tool call was executed by the provider (e.g. Anthropic computer use). |
|
||||
| `»» provider_metadata` | array | false | | Provider metadata holds provider-specific response metadata (e.g. Anthropic cache control hints) as raw JSON. Internal only: stripped by db2sdk before API responses. |
|
||||
| `»» result` | array | false | | |
|
||||
| `»» result_delta` | string | false | | |
|
||||
| `»» result_reset` | boolean | false | | |
|
||||
| `»» signature` | string | false | | |
|
||||
| `»» skill_description` | string | false | | Skill description is the short description from the skill's SKILL.md frontmatter. |
|
||||
| `»» skill_dir` | string | false | | Skill dir is the absolute path to the skill directory inside the workspace filesystem. Internal only: used by read_skill/read_skill_file tools to locate skill files. |
|
||||
| `»» skill_name` | string | false | | Skill name is the kebab-case name of a discovered skill from the workspace's .agents/skills/ directory. |
|
||||
| `»» source_id` | string | false | | |
|
||||
| `»» start_line` | integer | false | | |
|
||||
| `»» text` | string | false | | |
|
||||
| `»» title` | string | false | | |
|
||||
| `»» tool_call_id` | string | false | | |
|
||||
| `»» tool_name` | string | false | | |
|
||||
| `»» type` | [codersdk.ChatMessagePartType](schemas.md#codersdkchatmessageparttype) | false | | |
|
||||
| `»» url` | string | false | | |
|
||||
| `» last_model_config_id` | string(uuid) | false | | |
|
||||
| `» last_turn_summary` | string | false | | |
|
||||
| `» mcp_server_ids` | array | false | | |
|
||||
| `» organization_id` | string(uuid) | false | | |
|
||||
| `» owner_id` | string(uuid) | false | | |
|
||||
| `» parent_chat_id` | string(uuid) | false | | |
|
||||
| `» pin_order` | integer | false | | |
|
||||
| `» plan_mode` | [codersdk.ChatPlanMode](schemas.md#codersdkchatplanmode) | false | | |
|
||||
| `» root_chat_id` | string(uuid) | false | | |
|
||||
| `» status` | [codersdk.ChatStatus](schemas.md#codersdkchatstatus) | false | | |
|
||||
| `» title` | string | false | | |
|
||||
| `» updated_at` | string(date-time) | false | | |
|
||||
| `» warnings` | array | false | | |
|
||||
| `» workspace_id` | string(uuid) | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-----------------------------------|------------------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» agent_id` | string(uuid) | false | | |
|
||||
| `» archived` | boolean | false | | |
|
||||
| `» build_id` | string(uuid) | false | | |
|
||||
| `» children` | [codersdk.Chat](schemas.md#codersdkchat) | false | | Children holds child (subagent) chats nested under this root chat. Always initialized to an empty slice so the JSON field is present as []. Child chats cannot create their own subagents, so nesting depth is capped at 1 and this slice is always empty for child chats. |
|
||||
| `» client_type` | [codersdk.ChatClientType](schemas.md#codersdkchatclienttype) | false | | |
|
||||
| `» created_at` | string(date-time) | false | | |
|
||||
| `» diff_status` | [codersdk.ChatDiffStatus](schemas.md#codersdkchatdiffstatus) | false | | |
|
||||
| `»» additions` | integer | false | | |
|
||||
| `»» approved` | boolean | false | | |
|
||||
| `»» author_avatar_url` | string | false | | |
|
||||
| `»» author_login` | string | false | | |
|
||||
| `»» base_branch` | string | false | | |
|
||||
| `»» changed_files` | integer | false | | |
|
||||
| `»» changes_requested` | boolean | false | | |
|
||||
| `»» chat_id` | string(uuid) | false | | |
|
||||
| `»» commits` | integer | false | | |
|
||||
| `»» deletions` | integer | false | | |
|
||||
| `»» head_branch` | string | false | | |
|
||||
| `»» pr_number` | integer | false | | |
|
||||
| `»» pull_request_draft` | boolean | false | | |
|
||||
| `»» pull_request_state` | string | false | | |
|
||||
| `»» pull_request_title` | string | false | | |
|
||||
| `»» refreshed_at` | string(date-time) | false | | |
|
||||
| `»» reviewer_count` | integer | false | | |
|
||||
| `»» stale_at` | string(date-time) | false | | |
|
||||
| `»» url` | string | false | | |
|
||||
| `» files` | array | false | | |
|
||||
| `»» created_at` | string(date-time) | false | | |
|
||||
| `»» id` | string(uuid) | false | | |
|
||||
| `»» mime_type` | string | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» organization_id` | string(uuid) | false | | |
|
||||
| `»» owner_id` | string(uuid) | false | | |
|
||||
| `» has_unread` | boolean | false | | Has unread is true when assistant messages exist beyond the owner's read cursor, which updates on stream connect and disconnect. |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
| `» labels` | object | false | | |
|
||||
| `»» [any property]` | string | false | | |
|
||||
| `» last_error` | [codersdk.ChatError](schemas.md#codersdkchaterror) | false | | |
|
||||
| `»» detail` | string | false | | Detail is optional provider-specific context shown alongside the normalized error message when available. |
|
||||
| `»» kind` | [codersdk.ChatErrorKind](schemas.md#codersdkchaterrorkind) | false | | Kind classifies the error for consistent client rendering. |
|
||||
| `»» message` | string | false | | Message is the normalized, user-facing error message. |
|
||||
| `»» provider` | string | false | | Provider identifies the upstream model provider when known. |
|
||||
| `»» retryable` | boolean | false | | Retryable reports whether the underlying error is transient. |
|
||||
| `»» status_code` | integer | false | | Status code is the best-effort upstream HTTP status code. |
|
||||
| `» last_injected_context` | array | false | | Last injected context holds the most recently persisted injected context parts (AGENTS.md files and skills). It is updated only when context changes, on first workspace attach or agent change. |
|
||||
| `»» args` | array | false | | |
|
||||
| `»» args_delta` | string | false | | |
|
||||
| `»» completed_at` | string(date-time) | false | | Completed at is the time a reasoning part finished streaming, so reasoning duration can be computed as completed_at minus created_at. For interrupted reasoning, this is the interruption time. Absent when reasoning timestamp data was not recorded (e.g. messages persisted before this feature was added). |
|
||||
| `»» content` | string | false | | The code content from the diff that was commented on. |
|
||||
| `»» context_file_agent_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | Context file agent ID is the workspace agent that provided this context file. Used to detect when the agent changes (e.g. workspace rebuilt) so instruction files can be re-persisted with fresh content. |
|
||||
| `»»» uuid` | string | false | | |
|
||||
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
|
||||
| `»» context_file_content` | string | false | | Context file content holds the file content sent to the LLM. Internal only: stripped before API responses to keep payloads small. The backend reads it when building the prompt via partsToMessageParts. |
|
||||
| `»» context_file_directory` | string | false | | Context file directory is the working directory of the workspace agent. Internal only: same purpose as ContextFileOS. |
|
||||
| `»» context_file_os` | string | false | | Context file os is the operating system of the workspace agent. Internal only: used during prompt expansion so the LLM knows the OS even on turns where InsertSystem is not called. |
|
||||
| `»» context_file_path` | string | false | | Context file path is the absolute path of a file loaded into the LLM context (e.g. an AGENTS.md instruction file). |
|
||||
| `»» context_file_skill_meta_file` | string | false | | Context file skill meta file is the basename of the skill meta file (e.g. "SKILL.md") at the time of persistence. Internal only: restored on subsequent turns so the read_skill tool uses the correct filename even when the agent configured a non-default value. |
|
||||
| `»» context_file_truncated` | boolean | false | | Context file truncated indicates the file exceeded the 64KiB instruction file limit and was truncated. |
|
||||
| `»» created_at` | string(date-time) | false | | Created at is the timestamp this part carries. The semantics depend on the part type: for tool-call and tool-result parts it is the time the call was emitted or the result was produced (tool duration is the result's created_at minus the call's created_at); for reasoning parts it is the time reasoning started streaming. |
|
||||
| `»» data` | array | false | | |
|
||||
| `»» end_line` | integer | false | | |
|
||||
| `»» file_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | |
|
||||
| `»»» uuid` | string | false | | |
|
||||
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
|
||||
| `»» file_name` | string | false | | |
|
||||
| `»» is_error` | boolean | false | | |
|
||||
| `»» is_media` | boolean | false | | |
|
||||
| `»» mcp_server_config_id` | [uuid.NullUUID](schemas.md#uuidnulluuid) | false | | |
|
||||
| `»»» uuid` | string | false | | |
|
||||
| `»»» valid` | boolean | false | | Valid is true if UUID is not NULL |
|
||||
| `»» media_type` | string | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» provider_executed` | boolean | false | | Provider executed indicates the tool call was executed by the provider (e.g. Anthropic computer use). |
|
||||
| `»» provider_metadata` | array | false | | Provider metadata holds provider-specific response metadata (e.g. Anthropic cache control hints) as raw JSON. Internal only: stripped by db2sdk before API responses. |
|
||||
| `»» result` | array | false | | |
|
||||
| `»» result_delta` | string | false | | |
|
||||
| `»» result_reset` | boolean | false | | |
|
||||
| `»» signature` | string | false | | |
|
||||
| `»» skill_description` | string | false | | Skill description is the short description from the skill's SKILL.md frontmatter. |
|
||||
| `»» skill_dir` | string | false | | Skill dir is the absolute path to the skill directory inside the workspace filesystem. Internal only: used by read_skill/read_skill_file tools to locate skill files. |
|
||||
| `»» skill_name` | string | false | | Skill name is the kebab-case name of a discovered skill from the workspace's .agents/skills/ directory. |
|
||||
| `»» source_id` | string | false | | |
|
||||
| `»» start_line` | integer | false | | |
|
||||
| `»» text` | string | false | | |
|
||||
| `»» title` | string | false | | |
|
||||
| `»» tool_call_id` | string | false | | |
|
||||
| `»» tool_name` | string | false | | |
|
||||
| `»» type` | [codersdk.ChatMessagePartType](schemas.md#codersdkchatmessageparttype) | false | | |
|
||||
| `»» url` | string | false | | |
|
||||
| `» last_model_config_id` | string(uuid) | false | | |
|
||||
| `» last_turn_summary` | string | false | | |
|
||||
| `» mcp_server_ids` | array | false | | |
|
||||
| `» organization_id` | string(uuid) | false | | |
|
||||
| `» owner_id` | string(uuid) | false | | |
|
||||
| `» parent_chat_id` | string(uuid) | false | | |
|
||||
| `» pin_order` | integer | false | | |
|
||||
| `» plan_mode` | [codersdk.ChatPlanMode](schemas.md#codersdkchatplanmode) | false | | |
|
||||
| `» root_chat_id` | string(uuid) | false | | |
|
||||
| `» status` | [codersdk.ChatStatus](schemas.md#codersdkchatstatus) | false | | |
|
||||
| `» title` | string | false | | |
|
||||
| `» updated_at` | string(date-time) | false | | |
|
||||
| `» warnings` | array | false | | |
|
||||
| `» workspace_id` | string(uuid) | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
@@ -420,6 +422,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -545,6 +548,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -821,6 +825,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1000,6 +1005,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1125,6 +1131,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1385,6 +1392,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1510,6 +1518,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1631,6 +1640,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1708,6 +1718,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1837,6 +1848,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -1913,6 +1925,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2040,6 +2053,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2225,6 +2239,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2298,6 +2313,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2360,6 +2376,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2609,6 +2626,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2734,6 +2752,7 @@ Experimental: this endpoint is subject to change.
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
|
||||
Generated
+55
-39
@@ -2182,6 +2182,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2307,6 +2308,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2644,6 +2646,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2734,6 +2737,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2789,45 +2793,46 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------------------------|--------------------------------------------------------------|----------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `args` | array of integer | false | | |
|
||||
| `args_delta` | string | false | | |
|
||||
| `content` | string | false | | The code content from the diff that was commented on. |
|
||||
| `context_file_agent_id` | [uuid.NullUUID](#uuidnulluuid) | false | | Context file agent ID is the workspace agent that provided this context file. Used to detect when the agent changes (e.g. workspace rebuilt) so instruction files can be re-persisted with fresh content. |
|
||||
| `context_file_content` | string | false | | Context file content holds the file content sent to the LLM. Internal only: stripped before API responses to keep payloads small. The backend reads it when building the prompt via partsToMessageParts. |
|
||||
| `context_file_directory` | string | false | | Context file directory is the working directory of the workspace agent. Internal only: same purpose as ContextFileOS. |
|
||||
| `context_file_os` | string | false | | Context file os is the operating system of the workspace agent. Internal only: used during prompt expansion so the LLM knows the OS even on turns where InsertSystem is not called. |
|
||||
| `context_file_path` | string | false | | Context file path is the absolute path of a file loaded into the LLM context (e.g. an AGENTS.md instruction file). |
|
||||
| `context_file_skill_meta_file` | string | false | | Context file skill meta file is the basename of the skill meta file (e.g. "SKILL.md") at the time of persistence. Internal only: restored on subsequent turns so the read_skill tool uses the correct filename even when the agent configured a non-default value. |
|
||||
| `context_file_truncated` | boolean | false | | Context file truncated indicates the file exceeded the 64KiB instruction file limit and was truncated. |
|
||||
| `created_at` | string | false | | Created at records when this part was produced. Present on tool-call and tool-result parts so the frontend can compute tool execution duration. |
|
||||
| `data` | array of integer | false | | |
|
||||
| `end_line` | integer | false | | |
|
||||
| `file_id` | [uuid.NullUUID](#uuidnulluuid) | false | | |
|
||||
| `file_name` | string | false | | |
|
||||
| `is_error` | boolean | false | | |
|
||||
| `is_media` | boolean | false | | |
|
||||
| `mcp_server_config_id` | [uuid.NullUUID](#uuidnulluuid) | false | | |
|
||||
| `media_type` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `provider_executed` | boolean | false | | Provider executed indicates the tool call was executed by the provider (e.g. Anthropic computer use). |
|
||||
| `provider_metadata` | array of integer | false | | Provider metadata holds provider-specific response metadata (e.g. Anthropic cache control hints) as raw JSON. Internal only: stripped by db2sdk before API responses. |
|
||||
| `result` | array of integer | false | | |
|
||||
| `result_delta` | string | false | | |
|
||||
| `result_reset` | boolean | false | | |
|
||||
| `signature` | string | false | | |
|
||||
| `skill_description` | string | false | | Skill description is the short description from the skill's SKILL.md frontmatter. |
|
||||
| `skill_dir` | string | false | | Skill dir is the absolute path to the skill directory inside the workspace filesystem. Internal only: used by read_skill/read_skill_file tools to locate skill files. |
|
||||
| `skill_name` | string | false | | Skill name is the kebab-case name of a discovered skill from the workspace's .agents/skills/ directory. |
|
||||
| `source_id` | string | false | | |
|
||||
| `start_line` | integer | false | | |
|
||||
| `text` | string | false | | |
|
||||
| `title` | string | false | | |
|
||||
| `tool_call_id` | string | false | | |
|
||||
| `tool_name` | string | false | | |
|
||||
| `type` | [codersdk.ChatMessagePartType](#codersdkchatmessageparttype) | false | | |
|
||||
| `url` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------------------------|--------------------------------------------------------------|----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `args` | array of integer | false | | |
|
||||
| `args_delta` | string | false | | |
|
||||
| `completed_at` | string | false | | Completed at is the time a reasoning part finished streaming, so reasoning duration can be computed as completed_at minus created_at. For interrupted reasoning, this is the interruption time. Absent when reasoning timestamp data was not recorded (e.g. messages persisted before this feature was added). |
|
||||
| `content` | string | false | | The code content from the diff that was commented on. |
|
||||
| `context_file_agent_id` | [uuid.NullUUID](#uuidnulluuid) | false | | Context file agent ID is the workspace agent that provided this context file. Used to detect when the agent changes (e.g. workspace rebuilt) so instruction files can be re-persisted with fresh content. |
|
||||
| `context_file_content` | string | false | | Context file content holds the file content sent to the LLM. Internal only: stripped before API responses to keep payloads small. The backend reads it when building the prompt via partsToMessageParts. |
|
||||
| `context_file_directory` | string | false | | Context file directory is the working directory of the workspace agent. Internal only: same purpose as ContextFileOS. |
|
||||
| `context_file_os` | string | false | | Context file os is the operating system of the workspace agent. Internal only: used during prompt expansion so the LLM knows the OS even on turns where InsertSystem is not called. |
|
||||
| `context_file_path` | string | false | | Context file path is the absolute path of a file loaded into the LLM context (e.g. an AGENTS.md instruction file). |
|
||||
| `context_file_skill_meta_file` | string | false | | Context file skill meta file is the basename of the skill meta file (e.g. "SKILL.md") at the time of persistence. Internal only: restored on subsequent turns so the read_skill tool uses the correct filename even when the agent configured a non-default value. |
|
||||
| `context_file_truncated` | boolean | false | | Context file truncated indicates the file exceeded the 64KiB instruction file limit and was truncated. |
|
||||
| `created_at` | string | false | | Created at is the timestamp this part carries. The semantics depend on the part type: for tool-call and tool-result parts it is the time the call was emitted or the result was produced (tool duration is the result's created_at minus the call's created_at); for reasoning parts it is the time reasoning started streaming. |
|
||||
| `data` | array of integer | false | | |
|
||||
| `end_line` | integer | false | | |
|
||||
| `file_id` | [uuid.NullUUID](#uuidnulluuid) | false | | |
|
||||
| `file_name` | string | false | | |
|
||||
| `is_error` | boolean | false | | |
|
||||
| `is_media` | boolean | false | | |
|
||||
| `mcp_server_config_id` | [uuid.NullUUID](#uuidnulluuid) | false | | |
|
||||
| `media_type` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `provider_executed` | boolean | false | | Provider executed indicates the tool call was executed by the provider (e.g. Anthropic computer use). |
|
||||
| `provider_metadata` | array of integer | false | | Provider metadata holds provider-specific response metadata (e.g. Anthropic cache control hints) as raw JSON. Internal only: stripped by db2sdk before API responses. |
|
||||
| `result` | array of integer | false | | |
|
||||
| `result_delta` | string | false | | |
|
||||
| `result_reset` | boolean | false | | |
|
||||
| `signature` | string | false | | |
|
||||
| `skill_description` | string | false | | Skill description is the short description from the skill's SKILL.md frontmatter. |
|
||||
| `skill_dir` | string | false | | Skill dir is the absolute path to the skill directory inside the workspace filesystem. Internal only: used by read_skill/read_skill_file tools to locate skill files. |
|
||||
| `skill_name` | string | false | | Skill name is the kebab-case name of a discovered skill from the workspace's .agents/skills/ directory. |
|
||||
| `source_id` | string | false | | |
|
||||
| `start_line` | integer | false | | |
|
||||
| `text` | string | false | | |
|
||||
| `title` | string | false | | |
|
||||
| `tool_call_id` | string | false | | |
|
||||
| `tool_name` | string | false | | |
|
||||
| `type` | [codersdk.ChatMessagePartType](#codersdkchatmessageparttype) | false | | |
|
||||
| `url` | string | false | | |
|
||||
|
||||
## codersdk.ChatMessagePartType
|
||||
|
||||
@@ -2897,6 +2902,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -2974,6 +2980,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -3191,6 +3198,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -3337,6 +3345,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -3410,6 +3419,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -3472,6 +3482,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -3582,6 +3593,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -3766,6 +3778,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -4179,6 +4192,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -4255,6 +4269,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
@@ -6667,6 +6682,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
0
|
||||
],
|
||||
"args_delta": "string",
|
||||
"completed_at": "2019-08-24T14:15:22Z",
|
||||
"content": "string",
|
||||
"context_file_agent_id": {
|
||||
"uuid": "string",
|
||||
|
||||
Generated
+30
-6
@@ -2591,6 +2591,24 @@ export interface ChatQueuedMessage {
|
||||
export interface ChatReasoningPart {
|
||||
readonly type: "reasoning";
|
||||
readonly text: string;
|
||||
/**
|
||||
* CreatedAt is the timestamp this part carries. The semantics
|
||||
* depend on the part type: for tool-call and tool-result parts
|
||||
* it is the time the call was emitted or the result was
|
||||
* produced (tool duration is the result's created_at minus the
|
||||
* call's created_at); for reasoning parts it is the time
|
||||
* reasoning started streaming.
|
||||
*/
|
||||
readonly created_at?: string;
|
||||
/**
|
||||
* CompletedAt is the time a reasoning part finished streaming,
|
||||
* so reasoning duration can be computed as completed_at minus
|
||||
* created_at. For interrupted reasoning, this is the
|
||||
* interruption time. Absent when reasoning timestamp data was
|
||||
* not recorded (e.g. messages persisted before this feature
|
||||
* was added).
|
||||
*/
|
||||
readonly completed_at?: string;
|
||||
}
|
||||
|
||||
// From codersdk/chats.go
|
||||
@@ -2793,9 +2811,12 @@ export interface ChatToolCallPart {
|
||||
*/
|
||||
readonly provider_executed?: boolean;
|
||||
/**
|
||||
* CreatedAt records when this part was produced. Present on
|
||||
* tool-call and tool-result parts so the frontend can compute
|
||||
* tool execution duration.
|
||||
* CreatedAt is the timestamp this part carries. The semantics
|
||||
* depend on the part type: for tool-call and tool-result parts
|
||||
* it is the time the call was emitted or the result was
|
||||
* produced (tool duration is the result's created_at minus the
|
||||
* call's created_at); for reasoning parts it is the time
|
||||
* reasoning started streaming.
|
||||
*/
|
||||
readonly created_at?: string;
|
||||
}
|
||||
@@ -2817,9 +2838,12 @@ export interface ChatToolResultPart {
|
||||
*/
|
||||
readonly provider_executed?: boolean;
|
||||
/**
|
||||
* CreatedAt records when this part was produced. Present on
|
||||
* tool-call and tool-result parts so the frontend can compute
|
||||
* tool execution duration.
|
||||
* CreatedAt is the timestamp this part carries. The semantics
|
||||
* depend on the part type: for tool-call and tool-result parts
|
||||
* it is the time the call was emitted or the result was
|
||||
* produced (tool duration is the result's created_at minus the
|
||||
* call's created_at); for reasoning parts it is the time
|
||||
* reasoning started streaming.
|
||||
*/
|
||||
readonly created_at?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user