@sema-agent/server 1.241.0 → 1.243.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.
@@ -25,6 +25,7 @@ import { RateLimiter } from "../observability/rate-limit.js";
25
25
  import { withPrincipal } from "../observability/principal-context.js";
26
26
  import { projectEvents, runSummary, mapTraceEvent, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
27
27
  import { createLedgerSink } from "../trace/ledger-sink.js";
28
+ import { cacheFamilyOfMirror } from "../budget.js";
28
29
  import { projectArtifacts } from "../trace/artifacts.js";
29
30
  import { composeSupervisorCost, infraCost, infraUsageFromEvents, hasInfraPricing } from "../finance/cost-taxonomy.js";
30
31
  import { redactSecrets, redactDeep, redactedPreview } from "../trace/redact.js";
@@ -329,6 +330,10 @@ export function createHttpServer(deps) {
329
330
  port: deps.config.port,
330
331
  ...(deps.config.directDoorActive ? { directDoorActive: true } : {}),
331
332
  ...(restart ? { restartRequired: true, restart: { reasons: restart.reasons, version: restart.version, since: restart.since } } : {}),
333
+ ...(() => {
334
+ const stuck = deps.planeDeferredState?.();
335
+ return stuck ? { modelPlaneDeferred: { version: stuck.version, since: stuck.since, noHandoff: true, ...(stuck.blocked ? { blockedReasons: stuck.blocked } : {}) } } : {};
336
+ })(),
332
337
  });
333
338
  return;
334
339
  }
@@ -659,6 +664,8 @@ export function createHttpServer(deps) {
659
664
  subagentSteer: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
660
665
  subagentResume: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
661
666
  taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: false, hooks: deps.config.requirePrincipal !== true },
667
+ appendSystemPrompt: true,
668
+ compactionModel: true,
662
669
  taskAgents: deps.config.requirePrincipal !== true,
663
670
  retainBackgroundProcesses: deps.config.requirePrincipal !== true,
664
671
  interactiveTools: true,
@@ -670,6 +677,7 @@ export function createHttpServer(deps) {
670
677
  mcpElicitation: Boolean(deps.elicitation),
671
678
  askUserQuestion: Boolean(deps.question),
672
679
  toolApproval: Boolean(deps.toolApproval),
680
+ sideQuery: true,
673
681
  sessionList: Boolean(deps.sessionStorage?.listSessions ?? deps.runStore?.listSessions),
674
682
  sessionSearch: Boolean(deps.sessionStorage?.listSessions ?? deps.runStore?.listSessions),
675
683
  sessionFork: Boolean(deps.sessionStorage?.fork),
@@ -708,6 +716,77 @@ export function createHttpServer(deps) {
708
716
  sendJson(res, 200, { models, default: deps.config.model.id, defaultModel: { id: deps.config.model.id, ...(defaultName ? { name: defaultName } : {}) } });
709
717
  return;
710
718
  }
719
+ if (req.method === "POST" && url === "/v1/side-query") {
720
+ const ac = new AbortController();
721
+ const onClose = () => ac.abort();
722
+ res.on("close", onClose);
723
+ try {
724
+ if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)))
725
+ return;
726
+ const principal = gatedPrincipal(req, deps.config);
727
+ if (deps.config.requirePrincipal && !principal) {
728
+ sendJson(res, 401, { error: `missing principal header '${deps.config.principalHeader}'` });
729
+ return;
730
+ }
731
+ let body;
732
+ try {
733
+ const raw = await readJson(req);
734
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
735
+ throw new HttpError(400, "body must be a JSON object (SideQuerySpec shape)");
736
+ body = raw;
737
+ }
738
+ catch (e) {
739
+ const he = e instanceof HttpError ? e : new HttpError(400, msg(e));
740
+ sendJson(res, he.status, { error: he.message });
741
+ return;
742
+ }
743
+ if (!Array.isArray(body.messages) || body.messages.length === 0) {
744
+ sendJson(res, 400, { error: "messages must be a non-empty array (SideQueryMessage[])" });
745
+ return;
746
+ }
747
+ if (body.thinking !== undefined && !isThinkingLevel(body.thinking)) {
748
+ sendJson(res, 400, { error: "invalid thinking level" });
749
+ return;
750
+ }
751
+ if (ac.signal.aborted)
752
+ return;
753
+ const spec = {
754
+ messages: body.messages,
755
+ ...(typeof body.model === "string" ? { model: body.model } : {}),
756
+ ...(typeof body.modelRole === "string" ? { modelRole: body.modelRole } : {}),
757
+ ...(isThinkingLevel(body.thinking) ? { thinking: body.thinking } : {}),
758
+ ...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}),
759
+ ...(Array.isArray(body.tools) ? { tools: body.tools } : {}),
760
+ ...(typeof body.maxOutputTokens === "number" && Number.isFinite(body.maxOutputTokens) && body.maxOutputTokens > 0 ? { maxOutputTokens: Math.floor(body.maxOutputTokens) } : {}),
761
+ signal: ac.signal,
762
+ };
763
+ try {
764
+ const catalogSnapshot = Object.values(deps.runner.agentCatalog?.models ?? {}).map((m) => ({ id: m?.id, api: m?.api, params: m?.params?.promptCacheFamily ? { promptCacheFamily: m.params.promptCacheFamily } : undefined }));
765
+ const result = await deps.runner.sideQuery(spec);
766
+ const rr = result;
767
+ const modelId = typeof rr.model === "string" ? rr.model : "unknown";
768
+ const byId = catalogSnapshot.filter((m) => m.id === modelId);
769
+ const families = new Set(byId.map((m) => cacheFamilyOfMirror(m)));
770
+ const family = families.size === 1 ? [...families][0] : undefined;
771
+ deps.sideQueryAccounting?.(principal, { model: modelId, ...(family ? { family } : {}), usage: rr.usage });
772
+ deps.metrics?.inc?.("side_query_total", { result: "ok" });
773
+ if (!res.writableEnded)
774
+ sendJson(res, 200, result);
775
+ }
776
+ catch (e) {
777
+ deps.metrics?.inc?.("side_query_total", { result: "error" });
778
+ if (!res.writableEnded && !ac.signal.aborted) {
779
+ const m = msg(e);
780
+ const isValidation = /^Unknown model ref |^No model for role |requires a non-empty messages array/.test(m);
781
+ sendJson(res, isValidation ? 400 : 500, { error: redactSecrets(m) });
782
+ }
783
+ }
784
+ }
785
+ finally {
786
+ res.off("close", onClose);
787
+ }
788
+ return;
789
+ }
711
790
  if (req.method === "POST" && (url === "/v1/tasks" || url === "/v1/tasks/stream")) {
712
791
  const rawIdem = headerStr(req.headers["idempotency-key"]);
713
792
  const idemKey = rawIdem ? scopedIdempotencyKey(rawIdem, source, gatedPrincipal(req, deps.config)) : undefined;
@@ -4090,6 +4169,16 @@ export function createHttpServer(deps) {
4090
4169
  sendJson(res, 400, { error: `systemPrompt must be at most ${MAX_SYSTEM_PROMPT_CHARS} characters` });
4091
4170
  return null;
4092
4171
  }
4172
+ if (body.appendSystemPrompt !== undefined) {
4173
+ if (typeof body.appendSystemPrompt !== "string" || body.appendSystemPrompt.length === 0) {
4174
+ sendJson(res, 400, { error: "appendSystemPrompt must be a non-empty string" });
4175
+ return null;
4176
+ }
4177
+ if (body.appendSystemPrompt.length > MAX_SYSTEM_PROMPT_CHARS) {
4178
+ sendJson(res, 400, { error: `appendSystemPrompt must be at most ${MAX_SYSTEM_PROMPT_CHARS} characters` });
4179
+ return null;
4180
+ }
4181
+ }
4093
4182
  if (body.cwd !== undefined && !isValidCwd(body.cwd)) {
4094
4183
  sendJson(res, 400, { error: "cwd must be a non-empty absolute host path" });
4095
4184
  return null;
@@ -4121,6 +4210,13 @@ export function createHttpServer(deps) {
4121
4210
  sendJson(res, 400, { error: `settings.outputStyle must be at most ${MAX_SETTINGS_OUTPUT_STYLE_CHARS} characters` });
4122
4211
  return null;
4123
4212
  }
4213
+ if (typeof st.outputStyle === "string" &&
4214
+ st.outputStyle.length > 0 &&
4215
+ typeof body.appendSystemPrompt === "string" &&
4216
+ body.appendSystemPrompt.length + 2 + st.outputStyle.length > MAX_SYSTEM_PROMPT_CHARS) {
4217
+ sendJson(res, 400, { error: `appendSystemPrompt + settings.outputStyle fold into one system-prompt block — combined they must be at most ${MAX_SYSTEM_PROMPT_CHARS} characters (including the 2-char joiner)` });
4218
+ return null;
4219
+ }
4124
4220
  const perms = st.permissions;
4125
4221
  if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
4126
4222
  for (const k of ["allow", "deny", "ask"]) {
@@ -4352,6 +4448,21 @@ export function createHttpServer(deps) {
4352
4448
  return null;
4353
4449
  }
4354
4450
  }
4451
+ if (body.compactionModel !== undefined) {
4452
+ if (typeof body.compactionModel !== "string" || body.compactionModel.length === 0) {
4453
+ sendJson(res, 400, { error: "compactionModel must be a non-empty string (a configured catalog name, tier word, or model id)" });
4454
+ return null;
4455
+ }
4456
+ const bare = deps.config.models ?? {};
4457
+ const catalog = expandTiers(bare, deps.config.tiers ?? {}) ?? bare;
4458
+ if (matchCatalogModel(body.compactionModel, catalog) === undefined) {
4459
+ sendJson(res, 400, {
4460
+ error: `unknown compactionModel "${body.compactionModel.slice(0, 120)}" — not in the configured catalog (name, tier word, or id)`,
4461
+ available: Object.keys(catalog).filter((n) => n !== "default"),
4462
+ });
4463
+ return null;
4464
+ }
4465
+ }
4355
4466
  if (body.attachments !== undefined) {
4356
4467
  const a = body.attachments;
4357
4468
  if (typeof a !== "object" || a === null || Array.isArray(a)) {
@@ -4394,7 +4505,7 @@ export function createHttpServer(deps) {
4394
4505
  }
4395
4506
  try {
4396
4507
  const auth = deps.authorize ? await deps.authorize({ req, body }) : undefined;
4397
- return { spec: await deps.resolveSpec(body, req, auth), auth, verify, cascade, jobId: body.jobId, body };
4508
+ return { spec: await deps.resolveSpec(body, req, auth, { leg: "fresh" }), auth, verify, cascade, jobId: body.jobId, body };
4398
4509
  }
4399
4510
  catch (err) {
4400
4511
  if (err instanceof HttpError) {
@@ -5598,7 +5709,8 @@ function headerStr(v) {
5598
5709
  return Array.isArray(v) ? v[0] : v;
5599
5710
  }
5600
5711
  function isBillableSubmitPath(url) {
5601
- return (url === "/v1/tasks" ||
5712
+ return (url === "/v1/side-query" ||
5713
+ url === "/v1/tasks" ||
5602
5714
  url === "/v1/tasks/stream" ||
5603
5715
  url === "/v1/runs" ||
5604
5716
  url === "/v1/leader" ||