- Implement stream.go with sideband passthrough support - Update chat_handler.go with streaming handler changes - Add run_dispatch.go with new dispatch service logic - Add server tests for streaming and sideband functionality - Archive old plan/code-review docs for cloud-G07
117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
package service
|
|
|
|
import (
|
|
"sync"
|
|
|
|
edgeevents "iop/apps/edge/internal/events"
|
|
edgenode "iop/apps/edge/internal/node"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
const (
|
|
DefaultSessionID = "default"
|
|
DefaultTimeoutSec = 30
|
|
)
|
|
|
|
// Service is the surface-neutral application service shared by the console,
|
|
// HTTP/A2A input surfaces, and the Control Plane connector. Its responsibilities
|
|
// are split across this package by concern: status_provider.go (node snapshots,
|
|
// capabilities, health status), run_dispatch.go (run lifecycle), node_command.go
|
|
// (node command transport), and control_command.go (Control Plane command
|
|
// execution).
|
|
type Service struct {
|
|
mu sync.RWMutex
|
|
registry *edgenode.Registry
|
|
events *edgeevents.Bus
|
|
nodeStore *edgenode.NodeStore
|
|
queue *modelQueueManager
|
|
modelCatalog []config.ModelCatalogEntry
|
|
tunnels *providerTunnelRouter
|
|
}
|
|
|
|
func New(registry *edgenode.Registry, events *edgeevents.Bus) *Service {
|
|
s := &Service{registry: registry, events: events, tunnels: newProviderTunnelRouter()}
|
|
if events != nil {
|
|
q := newModelQueueManager(nil)
|
|
q.startEventWatcher(events)
|
|
s.queue = q
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *Service) SetNodeStore(store *edgenode.NodeStore) {
|
|
s.mu.Lock()
|
|
s.nodeStore = store
|
|
s.mu.Unlock()
|
|
if s.queue != nil {
|
|
s.queue.setStore(store)
|
|
}
|
|
}
|
|
|
|
// SetRuntimeConfig atomically replaces the runtime config snapshot used by
|
|
// dispatch and status readers. A reader observes either the old node
|
|
// store/catalog pair or the new pair, never a mix.
|
|
func (s *Service) SetRuntimeConfig(store *edgenode.NodeStore, catalog []config.ModelCatalogEntry) {
|
|
catalog = cloneModelCatalog(catalog)
|
|
s.mu.Lock()
|
|
s.nodeStore = store
|
|
s.modelCatalog = catalog
|
|
s.mu.Unlock()
|
|
if s.queue != nil {
|
|
s.queue.setStore(store)
|
|
}
|
|
}
|
|
|
|
// SetModelCatalog provides the top-level provider-pool model catalog to the
|
|
// service. Must be called before the first provider-pool SubmitRun.
|
|
func (s *Service) SetModelCatalog(catalog []config.ModelCatalogEntry) {
|
|
s.mu.Lock()
|
|
s.modelCatalog = cloneModelCatalog(catalog)
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *Service) runtimeConfigSnapshot() (*edgenode.NodeStore, []config.ModelCatalogEntry) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.nodeStore, cloneModelCatalog(s.modelCatalog)
|
|
}
|
|
|
|
func cloneModelCatalog(catalog []config.ModelCatalogEntry) []config.ModelCatalogEntry {
|
|
if len(catalog) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]config.ModelCatalogEntry, len(catalog))
|
|
for i, entry := range catalog {
|
|
out[i] = entry
|
|
if entry.Providers != nil {
|
|
out[i].Providers = make(map[string]string, len(entry.Providers))
|
|
for k, v := range entry.Providers {
|
|
out[i].Providers[k] = v
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *Service) ListNodes() []*edgenode.NodeEntry {
|
|
return s.registry.All()
|
|
}
|
|
|
|
// NormalizeSessionID maps an empty session id to the default session.
|
|
func NormalizeSessionID(id string) string {
|
|
if id == "" {
|
|
return DefaultSessionID
|
|
}
|
|
return id
|
|
}
|
|
|
|
func normalizeTimeoutSec(timeoutSec int) int {
|
|
if timeoutSec <= 0 {
|
|
return DefaultTimeoutSec
|
|
}
|
|
return timeoutSec
|
|
}
|
|
|
|
func nodeLabel(entry *edgenode.NodeEntry) string {
|
|
return entry.DisplayLabel()
|
|
}
|