@segmentstream/agent 0.1.2 → 0.1.3

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.
package/README.md CHANGED
@@ -51,17 +51,15 @@ import { segmentstream } from "@segmentstream/agent";
51
51
 
52
52
  export default defineAgent({
53
53
  defaultTools: false,
54
- model: segmentstream.model({
55
- default: "spacexai/grok-4.7",
56
- byRole: { finance: "anthropic/claude-opus-5.5" },
57
- preview: "google/gemini-flash",
58
- }),
54
+ model: segmentstream.model(),
59
55
  reasoning: "medium",
60
56
  limits: { maxTokenCostUsdPerSession: 2 },
61
57
  });
62
58
  ```
63
59
 
64
- `segmentstream.model()` resolves on `session.started`. On a preview deployment it returns `preview`
60
+ `segmentstream.model()` resolves on `session.started` from `models.chat` in `agent/segmentstream.json`
61
+ (`{ "default", "byRole", "preview" }`), where the platform reads it too; an inline
62
+ `segmentstream.model({ default, byRole, preview })` works the same. On a preview deployment it returns `preview`
65
63
  when set. Otherwise it returns the model of the first `byRole` entry whose role the member holds, in
66
64
  the order written, and `default` when none matches. The roles come from the member's pairing, not
67
65
  from the message. The model is never switched mid-session, which keeps the provider's prompt cache.
@@ -118,7 +116,8 @@ share a group chat's session never share a client.
118
116
  proxy. The token stays in SegmentStream.
119
117
  - **Webhook.** A webhook is accepted only with `X-Telegram-Bot-Api-Secret-Token` equal to
120
118
  `SEGMENTSTREAM_AGENT_WEBHOOK_SECRET`, compared in constant time. The platform generates the
121
- secret and registers the webhook after each production deployment.
119
+ secret and registers the webhook after each production deployment, and sets the bot's menu
120
+ button, shown in private chats, to open the workspace app.
122
121
  - **Production only.** Outside `VERCEL_ENV=production` the route answers 200 and does nothing, so a
123
122
  preview never answers a real chat. Without `channels.telegram` it answers 404.
124
123
  - **Pairing.** Messages from bots and channel posts are dropped. The sender's Telegram account is
@@ -30,6 +30,16 @@ export interface SegmentStreamAgentConfigInput {
30
30
  };
31
31
  };
32
32
  readonly models?: {
33
+ /**
34
+ * The chat model `segmentstream.model()` chooses per session: `default` for everyone, one per
35
+ * member role in `byRole` (the first role the member holds wins, in the order written) and
36
+ * `preview` for preview deployments.
37
+ */
38
+ readonly chat?: {
39
+ readonly default: string;
40
+ readonly byRole?: Readonly<Record<string, string>>;
41
+ readonly preview?: string;
42
+ };
33
43
  /** A Gateway transcription model, or `false` to leave voice messages untranscribed. */
34
44
  readonly stt?: string | boolean;
35
45
  /** The Gateway decision model of the inbound router. */
@@ -44,12 +54,26 @@ export interface TelegramSettings {
44
54
  readonly connection: string;
45
55
  readonly policy: TelegramPolicy;
46
56
  }
57
+ /** The chat models `segmentstream.model()` chooses between, as AI Gateway model ids. */
58
+ export interface ChatModelChoice {
59
+ /** The model for every session no other rule picks. */
60
+ readonly default: string;
61
+ /**
62
+ * A model per member role. The first entry whose role the member holds wins, in the order
63
+ * written, so put the more specific roles first.
64
+ */
65
+ readonly byRole?: Readonly<Record<string, string>>;
66
+ /** The model of preview deployments, so a preview never spends on a large model. */
67
+ readonly preview?: string;
68
+ }
47
69
  /** The validated configuration with defaults applied. It stays JSON-serializable. */
48
70
  export interface SegmentStreamAgentConfig {
49
71
  readonly channels: {
50
72
  readonly telegram: TelegramSettings | null;
51
73
  };
52
74
  readonly models: {
75
+ /** `null` when `agent.ts` passes the choice to `segmentstream.model()` itself. */
76
+ readonly chat: ChatModelChoice | null;
53
77
  readonly stt: string | false;
54
78
  readonly decision: string;
55
79
  };
@@ -72,6 +72,58 @@ function modelId(input) {
72
72
  }
73
73
  return input.value;
74
74
  }
75
+ function chatModelChoice(value, issues) {
76
+ const path = ["models", "chat"];
77
+ const chat = section({
78
+ value,
79
+ path,
80
+ allowed: [
81
+ "default",
82
+ "byRole",
83
+ "preview"
84
+ ],
85
+ issues
86
+ });
87
+ if (chat === void 0) return null;
88
+ const fallback = "";
89
+ if (chat.default === void 0) issues.push({
90
+ message: "is required",
91
+ path: [...path, "default"]
92
+ });
93
+ const defaultModel = modelId({
94
+ value: chat.default,
95
+ fallback,
96
+ path: [...path, "default"],
97
+ issues
98
+ });
99
+ const byRole = section({
100
+ value: chat.byRole,
101
+ path: [...path, "byRole"],
102
+ allowed: Object.keys(isRecord(chat.byRole) ? chat.byRole : {}),
103
+ issues
104
+ });
105
+ const roles = Object.fromEntries(Object.entries(byRole ?? {}).map(([role, model]) => [role, modelId({
106
+ value: model,
107
+ fallback,
108
+ path: [
109
+ ...path,
110
+ "byRole",
111
+ role
112
+ ],
113
+ issues
114
+ })]));
115
+ const preview = chat.preview === void 0 ? void 0 : modelId({
116
+ value: chat.preview,
117
+ fallback,
118
+ path: [...path, "preview"],
119
+ issues
120
+ });
121
+ return {
122
+ default: defaultModel,
123
+ ...byRole === void 0 ? {} : { byRole: roles },
124
+ ...preview === void 0 ? {} : { preview }
125
+ };
126
+ }
75
127
  function validateConfig(input) {
76
128
  const issues = [];
77
129
  const root = section({
@@ -93,7 +145,11 @@ function validateConfig(input) {
93
145
  const models = section({
94
146
  value: root?.models,
95
147
  path: ["models"],
96
- allowed: ["stt", "decision"],
148
+ allowed: [
149
+ "chat",
150
+ "stt",
151
+ "decision"
152
+ ],
97
153
  issues
98
154
  });
99
155
  const ontology = section({
@@ -103,6 +159,7 @@ function validateConfig(input) {
103
159
  issues
104
160
  });
105
161
  const telegram = telegramSettings(channels?.telegram, issues);
162
+ const chat = chatModelChoice(models?.chat, issues);
106
163
  const stt = models?.stt === false ? false : modelId({
107
164
  value: models?.stt,
108
165
  fallback: DEFAULT_STT_MODEL,
@@ -124,6 +181,7 @@ function validateConfig(input) {
124
181
  return { value: {
125
182
  channels: { telegram },
126
183
  models: {
184
+ chat,
127
185
  stt,
128
186
  decision
129
187
  },
@@ -1,5 +1,5 @@
1
1
  import { manifestOf } from "./config.js";
2
- import { model } from "./model.js";
2
+ import { type DynamicModel, type ModelChoice } from "./model.js";
3
3
  export declare const segmentstream: import("eve/extension").ExtensionHandle<{
4
4
  readonly "~standard": {
5
5
  readonly version: 1;
@@ -11,9 +11,9 @@ export declare const segmentstream: import("eve/extension").ExtensionHandle<{
11
11
  };
12
12
  };
13
13
  }> & {
14
- model: typeof model;
14
+ model: (choice?: ModelChoice) => DynamicModel;
15
15
  manifestOf: typeof manifestOf;
16
16
  };
17
17
  export default segmentstream;
18
- export { DEFAULT_DECISION_MODEL, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, manifestOf, type SegmentStreamAgentConfig, type SegmentStreamAgentConfigInput, type TelegramPolicy, type TelegramSettings, } from "./config.js";
18
+ export { DEFAULT_DECISION_MODEL, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, manifestOf, type ChatModelChoice, type SegmentStreamAgentConfig, type SegmentStreamAgentConfigInput, type TelegramPolicy, type TelegramSettings, } from "./config.js";
19
19
  export { chooseModel, model, type DynamicModel, type ModelChoice } from "./model.js";
@@ -9,7 +9,7 @@ import { chooseModel, model } from "./model.mjs";
9
9
  import { defineExtension } from "eve/extension";
10
10
  const extension = defineExtension({ config: configSchema });
11
11
  const segmentstream = Object.assign(extension, {
12
- model,
12
+ model: (choice) => model(choice ?? (() => extension.config.models.chat)),
13
13
  manifestOf
14
14
  });
15
15
  export { DEFAULT_DECISION_MODEL, DEFAULT_STT_MODEL, SegmentStreamAgentConfigError, chooseModel, segmentstream as default, segmentstream, manifestOf, model };
@@ -1,18 +1,9 @@
1
1
  import { type AgentModelDefinition, type AgentStaticModelDefinition } from "eve";
2
2
  import type { SessionAuthContext } from "eve/context";
3
+ import type { ChatModelChoice } from "./config.js";
3
4
  import { type Environment } from "./lib/environment.js";
4
5
  /** The models `segmentstream.model()` chooses between, as AI Gateway model ids. */
5
- export interface ModelChoice {
6
- /** The model for every session no other rule picks. */
7
- readonly default: string;
8
- /**
9
- * A model per member role. The first entry whose role the member holds wins, in the order
10
- * written, so put the more specific roles first.
11
- */
12
- readonly byRole?: Readonly<Record<string, string>>;
13
- /** The model of preview deployments, so a preview never spends on a large model. */
14
- readonly preview?: string;
15
- }
6
+ export type ModelChoice = ChatModelChoice;
16
7
  /** The dynamic form of an agent's `model`, which `defineAgent` accepts as the whole field. */
17
8
  export type DynamicModel = Exclude<AgentModelDefinition, AgentStaticModelDefinition>;
18
9
  /** The one model for a session started by `principal`. */
@@ -24,6 +15,8 @@ export declare function chooseModel(input: {
24
15
  /**
25
16
  * The agent's model, chosen once when a session starts from the member's roles and `VERCEL_ENV`.
26
17
  * Choosing once keeps the provider's prompt cache: a switch mid-session re-reads the history at
27
- * full price.
18
+ * full price. The choice is given here, or read when the session starts, from `models.chat` of
19
+ * `agent/segmentstream.json` through the mounted extension; a session without either fails to
20
+ * start with a message naming the missing setting.
28
21
  */
29
- export declare function model(choice: ModelChoice): DynamicModel;
22
+ export declare function model(choice: ModelChoice | (() => ModelChoice | null)): DynamicModel;
@@ -15,9 +15,13 @@ function chooseModel(input) {
15
15
  return choice.default;
16
16
  }
17
17
  function model(choice) {
18
- return defineDynamic({ events: { "session.started": (_event, context) => chooseModel({
19
- choice,
20
- principal: context.session.auth.current ?? context.session.auth.initiator
21
- }) } });
18
+ return defineDynamic({ events: { "session.started": (_event, context) => {
19
+ const chosen = typeof choice === "function" ? choice() : choice;
20
+ if (chosen === null) throw new Error("segmentstream.model() has no model to choose from: set models.chat in agent/segmentstream.json or pass the choice to segmentstream.model().");
21
+ return chooseModel({
22
+ choice: chosen,
23
+ principal: context.session.auth.current ?? context.session.auth.initiator
24
+ });
25
+ } } });
22
26
  }
23
27
  export { chooseModel, model };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@segmentstream/agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Mount a SegmentStream workspace's agent on eve: the ontology as tools, acting as the member who writes, and Telegram through the workspace's connection.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SegmentStream",