@downcity/agent 1.1.17 → 1.1.18

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.
@@ -4,15 +4,16 @@
4
4
  * 关键点(中文)
5
5
  * - 一个 `AgentCore` 只服务一个 agent 实例,不依赖进程级 singleton runtime。
6
6
  * - SDK `Agent`、实例绑定式 HTTP server、实例绑定式 local RPC 都应基于它工作。
7
- * - 宿主若需要统一 session 默认配置,应通过 `configureSession` 注入,而不是把策略写死在 SDK 中。
7
+ * - 宿主若需要统一 session 默认配置,应通过 `model` / `configureSession` 注入,而不是把策略写死在 SDK 中。
8
8
  */
9
9
 
10
10
  import fs from "fs-extra";
11
11
  import { nanoid } from "nanoid";
12
- import type { Tool } from "ai";
12
+ import type { LanguageModel, Tool } from "ai";
13
13
  import { Logger } from "@/utils/logger/Logger.js";
14
14
  import type { BaseService } from "@/service/builtins/BaseService.js";
15
15
  import type { AgentContext } from "@/core/AgentContextTypes.js";
16
+ import type { SessionPort } from "@/core/AgentContextTypes.js";
16
17
  import type { AgentRuntime } from "@/core/AgentCoreTypes.js";
17
18
  import type { DowncityConfig } from "@/types/config/DowncityConfig.js";
18
19
  import type { JsonValue } from "@/types/common/Json.js";
@@ -127,6 +128,7 @@ export class AgentCore {
127
128
  private readonly platform: AgentPlatformRuntime;
128
129
  private readonly env: Record<string, string>;
129
130
  private readonly globalEnv: Record<string, string>;
131
+ private readonly defaultModel?: LanguageModel;
130
132
  private readonly configureSessionHook?: AgentOptions["configureSession"];
131
133
  private instruction: string[];
132
134
  private servicesStartPromise: Promise<void> | null = null;
@@ -151,6 +153,7 @@ export class AgentCore {
151
153
  this.globalEnv = this.platform.getGlobalEnv?.() || {};
152
154
  this.env = this.platform.getAgentEnv?.(this.path) || {};
153
155
  this.instruction = normalizeInstructionInput(options.instruction);
156
+ this.defaultModel = options.model;
154
157
  this.configureSessionHook = options.configureSession;
155
158
  this.config = this.loadConfig();
156
159
  this.services = new Map<string, BaseService>();
@@ -400,9 +403,8 @@ export class AgentCore {
400
403
  paths: createAgentPathRuntime(this.path),
401
404
  pluginConfig: createAgentPluginConfigRuntime(this.path),
402
405
  platform: this.platform,
403
- getSession: (sessionId: string) => {
404
- return this.getOrCreateSession(sessionId).getServicePort() as never;
405
- },
406
+ getSession: (sessionId: string): SessionPort =>
407
+ this.getOrCreateSession(sessionId).getServicePort(),
406
408
  listExecutingSessionIds: () =>
407
409
  [...this.sessionsById.values()]
408
410
  .filter((session) => session.isExecuting())
@@ -410,10 +412,8 @@ export class AgentCore {
410
412
  getExecutingSessionCount: () =>
411
413
  [...this.sessionsById.values()].filter((session) => session.isExecuting()).length,
412
414
  services: this.services,
413
- } satisfies Omit<AgentRuntime, "getSession"> & {
414
- getSession(sessionId: string): never;
415
- };
416
- return runtime as unknown as AgentRuntime;
415
+ } satisfies AgentRuntime;
416
+ return runtime;
417
417
  }
418
418
 
419
419
  private createServiceContext(): AgentContext {
@@ -516,15 +516,24 @@ export class AgentCore {
516
516
  getInstructionSystemBlocks: () => this.loadInstructionSystemBlocks(),
517
517
  getServiceSystemBlocks: () => this.loadServiceSystemBlocks(),
518
518
  getPluginSystemBlocks: () => this.loadPluginSystemBlocks(),
519
+ ensureConfigured: async (session) => {
520
+ await this.configureSession(session);
521
+ },
519
522
  });
520
523
  this.sessionsById.set(resolvedSessionId, created);
521
524
  return created;
522
525
  }
523
526
 
524
527
  private async configureSession(session: Session): Promise<void> {
525
- if (!this.configureSessionHook) return;
526
528
  if (this.configuredSessionIds.has(session.id)) return;
527
- await this.configureSessionHook(session);
529
+ if (this.defaultModel) {
530
+ await session.set({
531
+ model: this.defaultModel,
532
+ });
533
+ }
534
+ if (this.configureSessionHook) {
535
+ await this.configureSessionHook(session);
536
+ }
528
537
  this.configuredSessionIds.add(session.id);
529
538
  }
530
539
  }
@@ -15,7 +15,7 @@ import type {
15
15
  AgentPluginConfigRuntime,
16
16
  } from "@/types/runtime/host/AgentHost.js";
17
17
  import type { BaseService } from "@/service/builtins/BaseService.js";
18
- import type { Executor } from "@session/Executor.js";
18
+ import type { SessionPort } from "@/core/AgentContextTypes.js";
19
19
 
20
20
  /**
21
21
  * AgentCore 启动早期的基础状态。
@@ -68,9 +68,13 @@ export interface AgentRuntimeBase {
68
68
  */
69
69
  export interface AgentRuntime extends AgentRuntimeBase {
70
70
  /**
71
- * 读取指定 sessionId 对应的内部 Executor 实例。
71
+ * 读取指定 sessionId 对应的 session 端口。
72
+ *
73
+ * 关键点(中文)
74
+ * - 返回值是统一的 `SessionPort`,而不是裸 `Executor`。
75
+ * - 这样 HTTP / service / chat queue / contact 等入口都能复用同一层会话装配与执行兜底。
72
76
  */
73
- getSession(sessionId: string): Executor;
77
+ getSession(sessionId: string): SessionPort;
74
78
  /**
75
79
  * 返回当前执行中的 sessionId 列表。
76
80
  */
@@ -54,6 +54,16 @@ export interface AgentOptions {
54
54
  */
55
55
  instruction?: string | string[];
56
56
 
57
+ /**
58
+ * 当前 agent 为新建 session 提供的默认模型实例。
59
+ *
60
+ * 关键点(中文)
61
+ * - SDK 仍不负责“选择哪个模型”,这里只接收宿主已经创建好的 `LanguageModel`。
62
+ * - 该模型会作为 session 首次执行前的默认注入值。
63
+ * - 若同时提供 `configureSession`,则先应用这里的默认模型,再允许宿主继续覆写。
64
+ */
65
+ model?: LanguageModel;
66
+
57
67
  /**
58
68
  * 当前 agent 显式持有的 service 实例集合。
59
69
  *
@@ -106,7 +116,9 @@ export interface AgentOptions {
106
116
  *
107
117
  * 关键点(中文)
108
118
  * - SDK 不负责默认模型策略,宿主可在这里统一为 session 注入 model 等运行配置。
109
- * - 该钩子只在 session 首次创建时触发一次,适合做实例级默认装配。
119
+ * - 若同时传入 `model`,则会先写入默认模型,再执行这里的宿主覆写逻辑。
120
+ * - 该钩子对每个 session 只触发一次,适合做实例级默认装配。
121
+ * - 触发时机可能来自显式 `agent.session()`,也可能来自该 session 的首次执行入口。
110
122
  */
111
123
  configureSession?: (session: Session) => Promise<void> | void;
112
124
  }
@@ -92,6 +92,15 @@ type SessionOptions = {
92
92
  * 读取当前 agent 显式注册 plugin 的 system blocks。
93
93
  */
94
94
  getPluginSystemBlocks: () => Promise<AgentSessionSystemBlock[]>;
95
+
96
+ /**
97
+ * 在执行前确保当前 session 已完成宿主侧默认配置。
98
+ *
99
+ * 关键点(中文)
100
+ * - 这里通常由 `AgentCore` 注入,用于补齐默认 model、宿主覆写等一次性装配。
101
+ * - 所有执行入口都应通过这里兜底,避免只在 SDK `agent.session()` 链路上做配置。
102
+ */
103
+ ensureConfigured?: (session: Session) => Promise<void>;
95
104
  };
96
105
 
97
106
  /**
@@ -107,6 +116,7 @@ export class Session {
107
116
  private readonly getInstructionSystemBlocks: SessionOptions["getInstructionSystemBlocks"];
108
117
  private readonly getServiceSystemBlocks: SessionOptions["getServiceSystemBlocks"];
109
118
  private readonly getPluginSystemBlocks: SessionOptions["getPluginSystemBlocks"];
119
+ private readonly ensureConfiguredHook?: SessionOptions["ensureConfigured"];
110
120
  private readonly historyStore: JsonlSessionHistoryStore;
111
121
  private readonly historyComposer: JsonlSessionHistoryComposer;
112
122
  private readonly executor: Executor;
@@ -114,6 +124,7 @@ export class Session {
114
124
  private createdAt = Date.now();
115
125
  private timezone = resolveSystemTimezone();
116
126
  private initializePromise: Promise<this> | null = null;
127
+ private ensureConfiguredPromise: Promise<void> | null = null;
117
128
  private servicePort: SessionPort | null = null;
118
129
 
119
130
  constructor(options: SessionOptions) {
@@ -125,6 +136,7 @@ export class Session {
125
136
  this.getInstructionSystemBlocks = options.getInstructionSystemBlocks;
126
137
  this.getServiceSystemBlocks = options.getServiceSystemBlocks;
127
138
  this.getPluginSystemBlocks = options.getPluginSystemBlocks;
139
+ this.ensureConfiguredHook = options.ensureConfigured;
128
140
  if (!this.id) {
129
141
  throw new Error("Session requires a non-empty sessionId");
130
142
  }
@@ -338,9 +350,10 @@ export class Session {
338
350
  if (!query) {
339
351
  throw new Error("session.run requires a non-empty query");
340
352
  }
353
+ await this.ensureReadyForExecution();
341
354
  if (!this.sessionConfig.model) {
342
355
  throw new Error(
343
- `Session "${this.id}" requires a configured model. Call session.set({ model }) first or let the host configure the session during creation.`,
356
+ `Session "${this.id}" requires a configured model. Pass model to new Agent({ model }), call session.set({ model }) first, or let the host configure the session during creation.`,
344
357
  );
345
358
  }
346
359
  await this.appendUserMessage({ text: query });
@@ -366,9 +379,10 @@ export class Session {
366
379
  if (!query) {
367
380
  throw new Error("session.stream requires a non-empty query");
368
381
  }
382
+ await this.ensureReadyForExecution();
369
383
  if (!this.sessionConfig.model) {
370
384
  throw new Error(
371
- `Session "${this.id}" requires a configured model. Call session.set({ model }) first or let the host configure the session during creation.`,
385
+ `Session "${this.id}" requires a configured model. Pass model to new Agent({ model }), call session.set({ model }) first, or let the host configure the session during creation.`,
372
386
  );
373
387
  }
374
388
  const queue = new AsyncQueue<AgentSessionStreamEvent>();
@@ -478,6 +492,9 @@ export class Session {
478
492
  sessionId: this.id,
479
493
  executor: this.executor,
480
494
  historyStore: this.historyStore,
495
+ ensureReadyForExecution: async () => {
496
+ await this.ensureReadyForExecution();
497
+ },
481
498
  touchMetadata: async () => {
482
499
  await this.touchMetadata();
483
500
  },
@@ -485,6 +502,27 @@ export class Session {
485
502
  return this.servicePort;
486
503
  }
487
504
 
505
+ /**
506
+ * 在执行前确保 session 已完成初始化与宿主装配。
507
+ */
508
+ async ensureReadyForExecution(): Promise<void> {
509
+ await this.initialize();
510
+ if (this.ensureConfiguredPromise) {
511
+ await this.ensureConfiguredPromise;
512
+ return;
513
+ }
514
+ this.ensureConfiguredPromise = (async () => {
515
+ if (!this.ensureConfiguredHook) return;
516
+ await this.ensureConfiguredHook(this);
517
+ })();
518
+ try {
519
+ await this.ensureConfiguredPromise;
520
+ } catch (error) {
521
+ this.ensureConfiguredPromise = null;
522
+ throw error;
523
+ }
524
+ }
525
+
488
526
  private async touchMetadata(): Promise<void> {
489
527
  await touchSessionMetadata({
490
528
  projectRoot: this.projectRoot,
@@ -25,6 +25,10 @@ export interface CreateSessionServicePortParams {
25
25
  * 当前 session 历史持久化端口。
26
26
  */
27
27
  historyStore: SessionHistoryStore;
28
+ /**
29
+ * 在执行前确保当前 session 已完成初始化与宿主级配置。
30
+ */
31
+ ensureReadyForExecution: () => Promise<void>;
28
32
  /**
29
33
  * session 更新后需要同步执行的持久化回调。
30
34
  */
@@ -42,6 +46,7 @@ export function createSessionServicePort(
42
46
  getExecutor: () => params.executor.getExecutor(),
43
47
  getHistoryStore: () => params.historyStore,
44
48
  run: async (runParams) => {
49
+ await params.ensureReadyForExecution();
45
50
  return await params.executor.run(runParams);
46
51
  },
47
52
  clearExecutor: () => {
@@ -717,7 +717,7 @@ export class Executor implements SessionExecutor {
717
717
  const model = this.getModel();
718
718
  if (!model) {
719
719
  throw new Error(
720
- `Executor for session "${this.sessionId}" requires a configured model`,
720
+ `Executor for session "${this.sessionId}" requires a configured model. Pass model to new Agent({ model }), call session.set({ model }), or let the host configure the session before execution.`,
721
721
  );
722
722
  }
723
723
  return model;