WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// PromptCacheStatus distinguishes a real cache miss from providers that do not
|
||||
// expose prompt-cache accounting. Treating both as zero makes fleet-level hit
|
||||
// rate dashboards misleading.
|
||||
type PromptCacheStatus string
|
||||
|
||||
const (
|
||||
PromptCacheStatusUnsupported PromptCacheStatus = "unsupported"
|
||||
PromptCacheStatusUnreported PromptCacheStatus = "unreported"
|
||||
PromptCacheStatusMiss PromptCacheStatus = "miss"
|
||||
PromptCacheStatusHit PromptCacheStatus = "hit"
|
||||
)
|
||||
|
||||
// TokenUsage holds token consumption statistics returned by the model API.
|
||||
type TokenUsage struct {
|
||||
PromptTokens int `json:"prompt_tokens"`
|
||||
CompletionTokens int `json:"completion_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
// CachedTokens is the legacy alias for CacheReadTokens. It remains on the
|
||||
// wire for compatibility with existing API consumers.
|
||||
CachedTokens int `json:"cached_tokens,omitempty"`
|
||||
CacheReadTokens int `json:"cache_read_tokens,omitempty"`
|
||||
CacheWriteTokens int `json:"cache_write_tokens,omitempty"`
|
||||
CacheMissTokens int `json:"cache_miss_tokens,omitempty"`
|
||||
CacheReported bool `json:"cache_reported"`
|
||||
CacheStatus PromptCacheStatus `json:"cache_status,omitempty"`
|
||||
}
|
||||
|
||||
// SetPromptCacheUsage normalizes provider-specific cache counters into the
|
||||
// shared usage model. promptTokens remains the provider's total input-token
|
||||
// count; read/write/miss are descriptive subsets and must not be added to it.
|
||||
func (u *TokenUsage) SetPromptCacheUsage(read, write, miss int, reported bool) {
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
if read < 0 {
|
||||
read = 0
|
||||
}
|
||||
if write < 0 {
|
||||
write = 0
|
||||
}
|
||||
if miss < 0 {
|
||||
miss = 0
|
||||
}
|
||||
u.CachedTokens = read
|
||||
u.CacheReadTokens = read
|
||||
u.CacheWriteTokens = write
|
||||
u.CacheMissTokens = miss
|
||||
u.CacheReported = reported
|
||||
switch {
|
||||
case !reported:
|
||||
u.CacheStatus = PromptCacheStatusUnreported
|
||||
case read > 0:
|
||||
u.CacheStatus = PromptCacheStatusHit
|
||||
default:
|
||||
u.CacheStatus = PromptCacheStatusMiss
|
||||
}
|
||||
}
|
||||
|
||||
// MarkPromptCacheUnsupported marks a provider/model path that cannot report
|
||||
// provider-side prompt-cache usage.
|
||||
func (u *TokenUsage) MarkPromptCacheUnsupported() {
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
u.SetPromptCacheUsage(0, 0, 0, false)
|
||||
u.CacheStatus = PromptCacheStatusUnsupported
|
||||
}
|
||||
|
||||
// Accumulate adds another call's usage into u, preserving the subset
|
||||
// semantics: prompt/completion/total and every cache counter sum
|
||||
// independently (cache counters stay subsets of the prompt count and are
|
||||
// never folded into it). CacheReported ORs, and the cache status is
|
||||
// recomputed from the combined counters so a single cache hit anywhere in
|
||||
// the accumulated calls reads as a hit.
|
||||
func (u *TokenUsage) Accumulate(other TokenUsage) {
|
||||
if u == nil {
|
||||
return
|
||||
}
|
||||
u.PromptTokens += other.PromptTokens
|
||||
u.CompletionTokens += other.CompletionTokens
|
||||
u.TotalTokens += other.TotalTokens
|
||||
u.CachedTokens += other.CachedTokens
|
||||
u.CacheReadTokens += other.CacheReadTokens
|
||||
u.CacheWriteTokens += other.CacheWriteTokens
|
||||
u.CacheMissTokens += other.CacheMissTokens
|
||||
u.CacheReported = u.CacheReported || other.CacheReported
|
||||
switch {
|
||||
case !u.CacheReported:
|
||||
u.CacheStatus = mergeUnreportedCacheStatus(u.CacheStatus, other.CacheStatus)
|
||||
case u.CacheReadTokens > 0:
|
||||
u.CacheStatus = PromptCacheStatusHit
|
||||
default:
|
||||
u.CacheStatus = PromptCacheStatusMiss
|
||||
}
|
||||
}
|
||||
|
||||
// mergeUnreportedCacheStatus folds the statuses of never-reported usage.
|
||||
// "unsupported" survives only while every accumulated call was itself
|
||||
// classified unsupported — the first accumulation adopts the incoming
|
||||
// classification, and any later call that is not known-unsupported (an
|
||||
// unreported or unclassified one) degrades the aggregate to unreported.
|
||||
func mergeUnreportedCacheStatus(accumulated, incoming PromptCacheStatus) PromptCacheStatus {
|
||||
if accumulated == "" {
|
||||
if incoming == "" {
|
||||
return PromptCacheStatusUnreported
|
||||
}
|
||||
return incoming
|
||||
}
|
||||
if accumulated == PromptCacheStatusUnsupported && incoming == PromptCacheStatusUnsupported {
|
||||
return PromptCacheStatusUnsupported
|
||||
}
|
||||
return PromptCacheStatusUnreported
|
||||
}
|
||||
|
||||
// PromptCacheHitRate returns cache-read tokens as a percentage of the
|
||||
// provider's prompt total. Zero when the prompt is empty. A ReAct turn that
|
||||
// reuses the system prefix therefore reads as a high hit rather than a miss.
|
||||
func (u TokenUsage) PromptCacheHitRate() float64 {
|
||||
if u.PromptTokens <= 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(u.CacheReadTokens) / float64(u.PromptTokens) * 100
|
||||
}
|
||||
|
||||
// Value persists the usage as a jsonb column (assistant messages carry the
|
||||
// turn's aggregate); nil writes SQL NULL. Mirrors the nullable-pointer
|
||||
// pattern APIPrincipalConfig uses.
|
||||
func (u *TokenUsage) Value() (driver.Value, error) {
|
||||
if u == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return json.Marshal(u)
|
||||
}
|
||||
|
||||
// Scan restores a jsonb usage column; NULL leaves the receiver zero-valued.
|
||||
func (u *TokenUsage) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var b []byte
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
b = v
|
||||
case string:
|
||||
b = []byte(v)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(b, u)
|
||||
}
|
||||
|
||||
// LLMToolCall represents a function/tool call from the LLM
|
||||
type LLMToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"` // "function"
|
||||
Function FunctionCall `json:"function"`
|
||||
ProviderMetadata ToolCallMetadata `json:"provider_metadata,omitempty"`
|
||||
|
||||
// ModelArguments and the resolution fields are request-local observability
|
||||
// state. ModelArguments preserves the exact JSON emitted by the model while
|
||||
// Function.Arguments is decoded to durable application identifiers before
|
||||
// tool execution. These fields must never be sent back to a provider or
|
||||
// persisted in chat history.
|
||||
ModelArguments string `json:"-"`
|
||||
ArgumentResolution string `json:"-"`
|
||||
UnresolvedHandles []string `json:"-"`
|
||||
}
|
||||
|
||||
// ToolCallMetadata carries provider-specific tool-call state that must round-trip
|
||||
// with the assistant tool call, without teaching core agent code vendor fields.
|
||||
type ToolCallMetadata map[string]json.RawMessage
|
||||
|
||||
// FunctionCall represents the function details
|
||||
type FunctionCall struct {
|
||||
Name string `json:"name"`
|
||||
Arguments string `json:"arguments"` // JSON string
|
||||
}
|
||||
|
||||
// ChatResponse chat response
|
||||
type ChatResponse struct {
|
||||
Content string `json:"content"`
|
||||
// ReasoningContent 是支持思考链的模型(DeepSeek thinking、小米 MiMo、vLLM reasoning 等)
|
||||
// 在本轮输出的推理内容。需要在后续多轮请求中原样回传给那些严格校验的供应商。
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
ToolCalls []LLMToolCall `json:"tool_calls,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
Usage TokenUsage `json:"usage"`
|
||||
|
||||
// AnswerStreamed reports whether the user-facing answer text was already
|
||||
// streamed live to the final-answer UI area during this round (i.e. the
|
||||
// model answered with plain content). When true, the natural-stop branch
|
||||
// must only emit the closing
|
||||
// Done marker for AnswerEventID instead of re-emitting the whole answer —
|
||||
// otherwise the answer would render twice and "jump" at end of stream.
|
||||
// Transient, never persisted.
|
||||
AnswerStreamed bool `json:"-"`
|
||||
// AnswerEventID is the EventBus event ID under which the live answer
|
||||
// chunks were streamed, so the natural-stop branch can close the same
|
||||
// stream with a Done marker. Empty when AnswerStreamed is false.
|
||||
AnswerEventID string `json:"-"`
|
||||
}
|
||||
|
||||
// Response type
|
||||
type ResponseType string
|
||||
|
||||
const (
|
||||
// Answer response type
|
||||
ResponseTypeAnswer ResponseType = "answer"
|
||||
// References response type
|
||||
ResponseTypeReferences ResponseType = "references"
|
||||
// Thinking response type (for agent thought process)
|
||||
ResponseTypeThinking ResponseType = "thinking"
|
||||
// Tool call response type (for agent tool invocations)
|
||||
ResponseTypeToolCall ResponseType = "tool_call"
|
||||
// Tool result response type (for agent tool results)
|
||||
ResponseTypeToolResult ResponseType = "tool_result"
|
||||
// Error response type
|
||||
ResponseTypeError ResponseType = "error"
|
||||
// Reflection response type (for agent reflection)
|
||||
ResponseTypeReflection ResponseType = "reflection"
|
||||
// Session title response type
|
||||
ResponseTypeSessionTitle ResponseType = "session_title"
|
||||
// Agent query response type (query received and processing started)
|
||||
ResponseTypeAgentQuery ResponseType = "agent_query"
|
||||
// Complete response type (agent complete)
|
||||
ResponseTypeComplete ResponseType = "complete"
|
||||
// ResponseTypeArtifactsPending is sent while skill/sandbox output is being
|
||||
// copied into persistent storage after the answer has already streamed.
|
||||
// The UI shows a toolbar placeholder until ResponseTypeComplete carries
|
||||
// the file list.
|
||||
ResponseTypeArtifactsPending ResponseType = "artifacts_pending"
|
||||
// ToolApprovalRequired: MCP tool marked dangerous — UI must collect user approval before execution continues
|
||||
ResponseTypeToolApprovalRequired ResponseType = "tool_approval_required"
|
||||
// ToolApprovalResolved: user approved/rejected (or timeout); informational for UI replay
|
||||
ResponseTypeToolApprovalResolved ResponseType = "tool_approval_resolved"
|
||||
// MCPOAuthRequired: an OAuth-enabled MCP service was invoked but the user
|
||||
// has not authorized it — UI must surface an "Authorize" prompt and the
|
||||
// agent pauses until authorization completes (or the wait times out).
|
||||
ResponseTypeMCPOAuthRequired ResponseType = "mcp_oauth_required"
|
||||
// MCPOAuthResolved: authorization completed / timed out / canceled;
|
||||
// informational for UI replay.
|
||||
ResponseTypeMCPOAuthResolved ResponseType = "mcp_oauth_resolved"
|
||||
// MemoryRecalled: the long-term memories injected into this answer, so
|
||||
// the UI can show and let the user delete what influenced it.
|
||||
ResponseTypeMemoryRecalled ResponseType = "memory_recalled"
|
||||
// ResponseTypeContextCompacted is older conversation summarized away to
|
||||
// fit the context window. Surfaced because it changes what the agent
|
||||
// remembers — an answer that forgets an earlier instruction is otherwise
|
||||
// indistinguishable from the model ignoring it.
|
||||
ResponseTypeContextCompacted ResponseType = "context_compacted"
|
||||
// ResponseTypeInstallPrompt is the instruction a skill install handed to
|
||||
// the installer agent. Only the skill install transcript emits this, and
|
||||
// it emits it first, so replaying the log alone shows what was asked for
|
||||
// — the console does not have to read the durable prompt row to caption
|
||||
// the run.
|
||||
ResponseTypeInstallPrompt ResponseType = "install_prompt"
|
||||
)
|
||||
|
||||
// StreamResponse stream response
|
||||
// FinishReasonIncomplete marks a stream that broke before the provider sent a
|
||||
// finish reason (read error, timeout, stall). Whatever content and tool calls
|
||||
// were collected are a partial response: callers must not treat them as a
|
||||
// completed turn.
|
||||
const FinishReasonIncomplete = "incomplete"
|
||||
|
||||
type StreamResponse struct {
|
||||
ID string `json:"id"`
|
||||
ResponseType ResponseType `json:"response_type"`
|
||||
Content string `json:"content"`
|
||||
Done bool `json:"done"`
|
||||
KnowledgeReferences References `json:"knowledge_references,omitempty"`
|
||||
SessionID string `json:"session_id,omitempty"`
|
||||
AssistantMessageID string `json:"assistant_message_id,omitempty"`
|
||||
ToolCalls []LLMToolCall `json:"tool_calls,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Usage *TokenUsage `json:"usage,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
}
|
||||
|
||||
// References references
|
||||
type References []*SearchResult
|
||||
|
||||
// Value implements the driver.Valuer interface, used to convert References to database values
|
||||
func (c References) Value() (driver.Value, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface, used to convert database values to References
|
||||
func (c *References) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
b, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(b, c)
|
||||
}
|
||||
Reference in New Issue
Block a user