@proteos/sdk 0.54.1 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import type { ProteosClient } from '../client.js'
2
2
  import { type AgentService, AgentServiceImpl } from './agents.js'
3
3
  import { type McpServerService, McpServerServiceImpl } from './mcp-servers.js'
4
+ import { type ModelService, ModelServiceImpl } from './models.js'
4
5
  import { type PromptService, PromptServiceImpl } from './prompts.js'
5
6
  import { type SessionService, SessionServiceImpl } from './sessions.js'
6
7
  import { type SkillService, SkillServiceImpl } from './skills.js'
@@ -12,7 +13,8 @@ import { type ToolsetService, ToolsetServiceImpl } from './toolsets.js'
12
13
  *
13
14
  * Manages the AI-agent configuration domain: Agents, Prompts (versioned),
14
15
  * Skills (versioned bundles), Tools, and MCP Servers — all org-scoped and keyed
15
- * by an immutable kebab-case `key`.
16
+ * by an immutable kebab-case `key` — plus the stateless model calls
17
+ * (`models.generate`, `models.decide`) and chat sessions.
16
18
  *
17
19
  * @example
18
20
  * ```ts
@@ -23,6 +25,15 @@ import { type ToolsetService, ToolsetServiceImpl } from './toolsets.js'
23
25
  *
24
26
  * const agents = await agent.agents.list().all();
25
27
  * const prompt = await agent.prompts.get('revenue-brief');
28
+ *
29
+ * const { answers } = await agent.models.decide({
30
+ * state: { message: 'My card was charged twice!' },
31
+ * questions: {
32
+ * refund_requested: { type: 'boolean', instructions: 'Does the customer ask for money back?' },
33
+ * topic: { type: 'choice', instructions: 'Topic?', criteria: { billing: 'charges', other: 'else' } },
34
+ * },
35
+ * });
36
+ * if (answers.topic.confidence > 0.8) route(answers.topic.choice);
26
37
  * ```
27
38
  */
28
39
  export class AgentClient {
@@ -38,6 +49,8 @@ export class AgentClient {
38
49
  readonly toolsets: ToolsetService
39
50
  /** Service for managing MCP server registrations. */
40
51
  readonly mcpServers: McpServerService
52
+ /** Service for stateless model calls: `generate` (one message out) and `decide` (calibrated typed answers). */
53
+ readonly models: ModelService
41
54
  /** Service for managing chat sessions (conversations + event log + stream). */
42
55
  readonly sessions: SessionService
43
56
 
@@ -53,6 +66,7 @@ export class AgentClient {
53
66
  this.tools = new ToolServiceImpl(client)
54
67
  this.toolsets = new ToolsetServiceImpl(client)
55
68
  this.mcpServers = new McpServerServiceImpl(client)
69
+ this.models = new ModelServiceImpl(client)
56
70
  this.sessions = new SessionServiceImpl(client)
57
71
  }
58
72
  }
@@ -60,6 +74,28 @@ export class AgentClient {
60
74
  // Re-export service interfaces
61
75
  export type { AgentService } from './agents.js'
62
76
  export type { McpServerService } from './mcp-servers.js'
77
+ // Re-export model-call types (generate + decide)
78
+ export type {
79
+ BooleanAnswer,
80
+ BooleanCriteria,
81
+ BooleanQuestion,
82
+ ChoiceAnswer,
83
+ ChoiceQuestion,
84
+ DecideRequest,
85
+ DecisionAnswer,
86
+ DecisionAnswerFor,
87
+ DecisionQuestion,
88
+ DecisionQuestions,
89
+ DecisionQuestionType,
90
+ DecisionResult,
91
+ GenerateRequest,
92
+ GenerationResult,
93
+ ModelStopReason,
94
+ ScoreAnswer,
95
+ ScoreQuestion,
96
+ StructuredText,
97
+ } from './model-types.js'
98
+ export type { ModelService } from './models.js'
63
99
  export type { PromptService } from './prompts.js'
64
100
  // Re-export session types
65
101
  export type {
@@ -110,12 +146,14 @@ export type {
110
146
  CreateMcpServerRequest,
111
147
  CreatePromptRequest,
112
148
  CreateToolRequest,
149
+ CreateToolsetRequest,
113
150
  DependentAgent,
114
151
  DependentSyncResult,
115
152
  ListAgentsOptions,
116
153
  ListMcpServersOptions,
117
154
  ListPromptsOptions,
118
155
  ListSkillsOptions,
156
+ ListToolsetsOptions,
119
157
  ListToolsOptions,
120
158
  McpBinding,
121
159
  McpConnectionState,
@@ -125,25 +163,23 @@ export type {
125
163
  McpServerOAuth,
126
164
  McpToolSummary,
127
165
  ModelConfig,
166
+ PlatformBinding,
128
167
  Prompt,
129
168
  PromptVersion,
169
+ QueryBinding,
130
170
  Skill,
131
171
  SkillBundle,
132
172
  SkillVersion,
133
173
  StartMcpOAuthResponse,
134
- PlatformBinding,
135
- QueryBinding,
136
174
  Tool,
137
175
  ToolBinding,
138
176
  ToolKind,
139
177
  Toolset,
140
178
  ToolsetKind,
141
179
  ToolsetToolSummary,
142
- CreateToolsetRequest,
143
- UpdateToolsetRequest,
144
- ListToolsetsOptions,
145
180
  UpdateAgentRequest,
146
181
  UpdateMcpServerRequest,
147
182
  UpdatePromptRequest,
148
183
  UpdateToolRequest,
184
+ UpdateToolsetRequest,
149
185
  } from './types.js'
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Types for the stateless model endpoints of the Proteos Agent Service API —
3
+ * `POST /agents/v1/models/generate` and `POST /agents/v1/models/decide`.
4
+ *
5
+ * Hand-written mirror of the Go models in `go.proteos.ai/model/agent`
6
+ * (`generation.go`, `decision.go` + `api/generation-requests.go`,
7
+ * `api/decision-requests.go`). Field names match the snake_case wire format
8
+ * exactly, including enum values.
9
+ */
10
+
11
+ import type { ContentBlock, ModelUsage, TextBlock } from './session-types.js'
12
+ import type { ModelConfig } from './types.js'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Generation (one message out)
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Why the model stopped generating. An open string so a new provider reason
20
+ * never breaks decoding; the key distinction is `end_turn` (complete) vs
21
+ * `max_tokens` (truncated).
22
+ */
23
+ export type ModelStopReason = 'end_turn' | 'max_tokens' | 'stop_sequence' | (string & {})
24
+
25
+ /**
26
+ * Body of `POST /agents/v1/models/generate` — a single, stateless model call:
27
+ * content blocks in, one assistant message out. No event log, no tools, no
28
+ * multi-turn state.
29
+ */
30
+ export interface GenerateRequest {
31
+ /** Model id + sampling knobs. Omitted (or an empty `model_id`) resolves to the service default. */
32
+ model?: Partial<ModelConfig>
33
+ /** Optional system prompt steering the response. */
34
+ system?: string
35
+ /** The message body the model answers — text and file blocks (a file-only body is valid). */
36
+ content: ContentBlock[]
37
+ /**
38
+ * JSON Schema of the structured reply. When set, generation is forced through a
39
+ * schema-constrained tool and the structured value comes back as `output`.
40
+ */
41
+ output_schema?: Record<string, unknown>
42
+ }
43
+
44
+ /** The assistant's reply to a {@link GenerateRequest}. */
45
+ export interface GenerationResult {
46
+ /** The returned text blocks (usually empty on a schema-constrained call). */
47
+ content: TextBlock[]
48
+ stop_reason?: ModelStopReason
49
+ /** The model that produced the reply (resolved id). */
50
+ model_id: string
51
+ usage: ModelUsage
52
+ /** The structured value of a schema-constrained generation; absent otherwise. */
53
+ output?: unknown
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Decisions (typed, calibrated answers out — no prose)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ /**
61
+ * The text shape a decision provider reads: a string, a JSON object, or an
62
+ * array — never `null`, a number, or a boolean. Carries the state under
63
+ * judgment, a question's instructions, every criteria description, and the
64
+ * legend echoed back on a score. Structure passes through verbatim, so
65
+ * instructions can be structured guidance (named fields such as `what` /
66
+ * `not_for` / `examples`, backtick paths into the state like
67
+ * `` `ticket.messages[0].text` ``).
68
+ */
69
+ export type StructuredText = string | Record<string, unknown> | unknown[]
70
+
71
+ /** Discriminates a {@link DecisionQuestion} and its {@link DecisionAnswer}. */
72
+ export type DecisionQuestionType = 'boolean' | 'choice' | 'score'
73
+
74
+ /** Describes the two outcomes of a {@link BooleanQuestion}. */
75
+ export interface BooleanCriteria {
76
+ true: StructuredText
77
+ false: StructuredText
78
+ }
79
+
80
+ /**
81
+ * "Is this statement true?" — answered with the calibrated probability that it
82
+ * is. `criteria` optionally spells out what makes the statement hold (true) or
83
+ * not (false) when the boundary is subtle.
84
+ */
85
+ export interface BooleanQuestion {
86
+ type: 'boolean'
87
+ instructions: StructuredText
88
+ criteria?: BooleanCriteria
89
+ }
90
+
91
+ /**
92
+ * "Which one of these?" — `criteria` maps each option name to its description;
93
+ * options are unordered. Option-count limits are the provider's.
94
+ */
95
+ export interface ChoiceQuestion<Option extends string = string> {
96
+ type: 'choice'
97
+ instructions: StructuredText
98
+ criteria: Record<Option, StructuredText>
99
+ }
100
+
101
+ /**
102
+ * "Where on this scale?" — `criteria` is the ordered list of level
103
+ * descriptions; the level number is the position (0-based). Every level must
104
+ * be described. Level-count limits are the provider's.
105
+ */
106
+ export interface ScoreQuestion {
107
+ type: 'score'
108
+ instructions: StructuredText
109
+ criteria: StructuredText[]
110
+ }
111
+
112
+ /** One atomic question over the shared state, discriminated by `type`. */
113
+ export type DecisionQuestion = BooleanQuestion | ChoiceQuestion | ScoreQuestion
114
+
115
+ /** Caller-keyed questions; answers come back under the same keys. */
116
+ export type DecisionQuestions = Record<string, DecisionQuestion>
117
+
118
+ /**
119
+ * Body of `POST /agents/v1/models/decide` — one text-only state and a map of
120
+ * atomic questions answered in parallel with calibrated probabilities.
121
+ *
122
+ * Only `model.model_id` is meaningful for a decision; the sampling knobs of
123
+ * {@link ModelConfig} are rejected with `invalid_decision_request`.
124
+ */
125
+ export interface DecideRequest<Questions extends DecisionQuestions = DecisionQuestions> {
126
+ model?: Partial<Pick<ModelConfig, 'model_id'>>
127
+ state: StructuredText
128
+ questions: Questions
129
+ }
130
+
131
+ /**
132
+ * Answer to a {@link BooleanQuestion}: `probability` is P(true); `boolean` is
133
+ * the argmax (probability > 0.5); `confidence` is |2p − 1|.
134
+ */
135
+ export interface BooleanAnswer {
136
+ type: 'boolean'
137
+ boolean: boolean
138
+ probability: number
139
+ confidence: number
140
+ }
141
+
142
+ /**
143
+ * Answer to a {@link ChoiceQuestion}: `choice` is the highest-probability
144
+ * option; `probabilities` is the full distribution keyed by option name.
145
+ */
146
+ export interface ChoiceAnswer<Option extends string = string> {
147
+ type: 'choice'
148
+ choice: Option
149
+ probabilities: Record<Option, number>
150
+ confidence: number
151
+ }
152
+
153
+ /**
154
+ * Answer to a {@link ScoreQuestion}: `score` is the probability-weighted mean
155
+ * of the level numbers (so it can fall between levels); `legend` echoes the
156
+ * level descriptions and `probabilities` the distribution, both indexed by
157
+ * level.
158
+ */
159
+ export interface ScoreAnswer {
160
+ type: 'score'
161
+ score: number
162
+ legend: StructuredText[]
163
+ probabilities: number[]
164
+ confidence: number
165
+ }
166
+
167
+ /**
168
+ * The calibrated answer to one question, discriminated by `type`. Every
169
+ * variant carries `confidence` = (n·peak − 1)/(n − 1) over its distribution,
170
+ * so gating code can threshold without switching on the type.
171
+ */
172
+ export type DecisionAnswer = BooleanAnswer | ChoiceAnswer | ScoreAnswer
173
+
174
+ /** The answer type a given question produces (drives typed `answers`). */
175
+ export type DecisionAnswerFor<Question extends DecisionQuestion> = Question extends BooleanQuestion
176
+ ? BooleanAnswer
177
+ : Question extends ChoiceQuestion<infer Option>
178
+ ? ChoiceAnswer<Option>
179
+ : Question extends ScoreQuestion
180
+ ? ScoreAnswer
181
+ : never
182
+
183
+ /**
184
+ * The provider's reply to a {@link DecideRequest}: one answer per question key
185
+ * (typed from the questions you sent), the model that produced them, and token
186
+ * usage.
187
+ */
188
+ export interface DecisionResult<Questions extends DecisionQuestions = DecisionQuestions> {
189
+ /** The model that produced the answers (resolved id, e.g. `jev-1.13.0`). */
190
+ model_id: string
191
+ answers: { [Key in keyof Questions]: DecisionAnswerFor<Questions[Key]> }
192
+ usage: ModelUsage
193
+ }
@@ -0,0 +1,67 @@
1
+ import type { ProteosClient } from '../client.js'
2
+ import type {
3
+ DecideRequest,
4
+ DecisionQuestions,
5
+ DecisionResult,
6
+ GenerateRequest,
7
+ GenerationResult,
8
+ } from './model-types.js'
9
+
10
+ const MODELS_BASE_PATH = '/agents/v1/models'
11
+
12
+ /**
13
+ * Service for the stateless model calls — one request in, one reply out, no
14
+ * session, no event log:
15
+ *
16
+ * - `generate` — free text or schema-constrained structured output from an LLM.
17
+ * - `decide` — calibrated typed answers (boolean | choice | score) with
18
+ * probabilities and confidence, for snap judgments inside code paths
19
+ * (routing, triage, classification, gates). No prose comes back; compose the
20
+ * answers in code and threshold on `confidence`.
21
+ *
22
+ * Both need the `agent-sessions:write` permission.
23
+ */
24
+ export interface ModelService {
25
+ /**
26
+ * Runs a single model call and returns the assistant message.
27
+ * @throws {ProteosError} 400 `invalid_generation_request` / `unsupported_file_type` /
28
+ * `model_provider_rejected_request`; 429 `model_provider_rate_limited`;
29
+ * 502 `model_provider_failed`.
30
+ */
31
+ generate(request: GenerateRequest): Promise<GenerationResult>
32
+ /**
33
+ * Answers typed questions about a text state with calibrated probabilities.
34
+ * `answers` is typed from the `questions` you pass, so
35
+ * `result.answers.topic.choice` narrows to the option names you declared.
36
+ *
37
+ * Semantic limits (option / level counts, empty questions, unknown model ids)
38
+ * are the provider's and surface as `decision_provider_rejected_request` with
39
+ * its message.
40
+ * @throws {ProteosError} 400 `invalid_decision_request` (shape) /
41
+ * `decision_provider_rejected_request` (provider rule); 429
42
+ * `decision_provider_rate_limited`; 502 `decision_provider_failed` /
43
+ * `decision_provider_invalid_response`; 503 `decision_provider_unavailable`
44
+ * when the provider key is not configured.
45
+ */
46
+ decide<Questions extends DecisionQuestions>(
47
+ request: DecideRequest<Questions>,
48
+ ): Promise<DecisionResult<Questions>>
49
+ }
50
+
51
+ export class ModelServiceImpl implements ModelService {
52
+ constructor(private readonly client: ProteosClient) {}
53
+
54
+ async generate(request: GenerateRequest): Promise<GenerationResult> {
55
+ return this.client.request<GenerationResult>('POST', `${MODELS_BASE_PATH}/generate`, request)
56
+ }
57
+
58
+ async decide<Questions extends DecisionQuestions>(
59
+ request: DecideRequest<Questions>,
60
+ ): Promise<DecisionResult<Questions>> {
61
+ return this.client.request<DecisionResult<Questions>>(
62
+ 'POST',
63
+ `${MODELS_BASE_PATH}/decide`,
64
+ request,
65
+ )
66
+ }
67
+ }
package/src/index.ts CHANGED
@@ -34,7 +34,7 @@
34
34
  * @packageDocumentation
35
35
  */
36
36
 
37
- // Agent client (agent-service: agents, prompts, skills, tools, mcp-servers, sessions)
37
+ // Agent client (agent-service: agents, prompts, skills, tools, mcp-servers, models, sessions)
38
38
  export type {
39
39
  ActionBinding,
40
40
  Agent,
@@ -43,6 +43,11 @@ export type {
43
43
  AgentResourceListOptions,
44
44
  AgentService,
45
45
  AppendEventRequest,
46
+ BooleanAnswer,
47
+ BooleanCriteria,
48
+ BooleanQuestion,
49
+ ChoiceAnswer,
50
+ ChoiceQuestion,
46
51
  ClientEventType,
47
52
  ClientToolSchema,
48
53
  ContentBlock,
@@ -53,12 +58,21 @@ export type {
53
58
  CreateSessionRequest,
54
59
  CreateToolRequest,
55
60
  CreateToolsetRequest,
61
+ DecideRequest,
62
+ DecisionAnswer,
63
+ DecisionAnswerFor,
64
+ DecisionQuestion,
65
+ DecisionQuestions,
66
+ DecisionQuestionType,
67
+ DecisionResult,
56
68
  DependentAgent,
57
69
  DependentSyncResult,
58
70
  EmptyPayload,
59
71
  ErrorPayload,
60
72
  EventType,
61
73
  FileBlock,
74
+ GenerateRequest,
75
+ GenerationResult,
62
76
  ListAgentsOptions,
63
77
  ListMcpServersOptions,
64
78
  ListPromptsOptions,
@@ -77,6 +91,8 @@ export type {
77
91
  ModelConfig,
78
92
  ModelRequestEndPayload,
79
93
  ModelRequestStartPayload,
94
+ ModelService,
95
+ ModelStopReason,
80
96
  ModelUsage,
81
97
  PlatformBinding,
82
98
  Prompt,
@@ -85,6 +101,8 @@ export type {
85
101
  QueryBinding,
86
102
  ReasoningPayload,
87
103
  ResultBlock,
104
+ ScoreAnswer,
105
+ ScoreQuestion,
88
106
  Session,
89
107
  SessionEvent,
90
108
  SessionEventEnvelope,
@@ -100,6 +118,7 @@ export type {
100
118
  SkillVersion,
101
119
  StartMcpOAuthResponse,
102
120
  StopReason,
121
+ StructuredText,
103
122
  TextBlock,
104
123
  Tool,
105
124
  ToolBinding,
package/src/meta/index.ts CHANGED
@@ -164,6 +164,12 @@ export type { MenuConfigurationService } from './menu-configurations.js'
164
164
  export type { ModuleService } from './modules.js'
165
165
  export type { PageExtensionService } from './page-extensions.js'
166
166
  export type { PageService } from './pages.js'
167
+ export {
168
+ isReservedAttributeName,
169
+ isTitleQueryable,
170
+ RECORD_TITLE_FIELD,
171
+ titleTemplateVariables,
172
+ } from './record-title.js'
167
173
  // Re-export types
168
174
  export type {
169
175
  // App types
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The virtual column that stands for a record's rendered title — the entity's
3
+ * Liquid `title_template` evaluated against the row. Lists show it, and the
4
+ * records query sorts and filters on it when the template is "simple". No
5
+ * attribute backs it, so the name is reserved: the backend rejects an
6
+ * attribute called `record_title` (mirror of `metamodel.RecordTitleField`).
7
+ */
8
+ export const RECORD_TITLE_FIELD = 'record_title'
9
+
10
+ /** True when `name` is taken by a virtual column and unavailable to attributes. */
11
+ export function isReservedAttributeName(name: string): boolean {
12
+ return name === RECORD_TITLE_FIELD
13
+ }
14
+
15
+ /**
16
+ * A simple template is literal text plus `{{ attribute }}` references and
17
+ * nothing else — no filters (`| upcase`), no tags (`{% if %}`), no dotted
18
+ * references. Only those compile to SQL, so only those make `record_title`
19
+ * sortable and filterable. Mirror of the Go `titletemplate.ParseSimple`
20
+ * grammar; the backend stays the authority and answers 400 when the two
21
+ * ever disagree.
22
+ */
23
+ export function isTitleQueryable(template: string | undefined): boolean {
24
+ // Nothing to order or match on without at least one attribute reference.
25
+ if (!template?.includes('{{')) return false
26
+ if (template.includes('{%') || template.includes('%}')) return false
27
+ let rest = template
28
+ while (rest !== '') {
29
+ const open = rest.indexOf('{{')
30
+ if (open < 0) return !rest.includes('}}')
31
+ if (rest.slice(0, open).includes('}}')) return false
32
+ rest = rest.slice(open + 2)
33
+ const close = rest.indexOf('}}')
34
+ if (close < 0) return false
35
+ if (!/^[a-z][a-z0-9_]*$/.test(rest.slice(0, close).trim())) return false
36
+ rest = rest.slice(close + 2)
37
+ }
38
+ return true
39
+ }
40
+
41
+ /** The attributes a simple template references, in order of first use; `[]` otherwise. */
42
+ export function titleTemplateVariables(template: string | undefined): string[] {
43
+ if (!template || !isTitleQueryable(template)) return []
44
+ const out: string[] = []
45
+ for (const match of template.matchAll(/\{\{\s*([a-z][a-z0-9_]*)\s*\}\}/g)) {
46
+ const name = match[1]
47
+ if (name && !out.includes(name)) out.push(name)
48
+ }
49
+ return out
50
+ }
@@ -165,7 +165,9 @@ export type PropertyType =
165
165
  | 'credentials_select'
166
166
  | 'button'
167
167
  /** Edits a platform attribute list (`Attribute[]`) via the shared attribute schema builder. */
168
- | 'attribute_schema'
168
+ /** Edits a decision-question map (model-decide `questions`) via the questions builder, with a raw-JSON mode. */
169
+ | 'decision_questions'
170
+ | 'attribute_schema'
169
171
  // Wave 2 (editor support lands with Phase 3):
170
172
  | 'resource_locator'
171
173
  | 'resource_mapper'