File size: 2,452 Bytes
67d18ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
export interface ZcodeTraceContext {
  requestId?: string;
  traceId?: string;
  queryId?: string;
  sessionId?: string;
  /** Optional override; one of "main" | "subagent" | "other" (ZCode 3.9.1 session types). */
  sessionType?: string;
}

const QUERY_PREFIX = "query_";
const SUBAGENT_PREFIX = "subagent_agent_";
const SESSION_PREFIXES = ["sess_", SUBAGENT_PREFIX];

/**
 * Resolve the `x-zcode-session-type` attribution value, mirroring the ZCode
 * 3.9.1 bundle's `resolveModelRequestSessionType`/`isModelRequestSessionType`.
 *
 * The bundle derives main/subagent/other from the agent orchestration context.
 * The proxy's equivalent signal is the client session id: ZCode-style harnesses
 * prefix subagent sessions with `subagent_agent_`. Every other forwarded
 * conversation turn is the moral equivalent of the main agent loop ("main");
 * an explicit ctx.sessionType wins when provided and valid.
 */
function resolveSessionType(ctx: ZcodeTraceContext): "main" | "subagent" | "other" {
  const explicit = ctx.sessionType?.trim();
  if (explicit === "main" || explicit === "subagent" || explicit === "other") return explicit;
  if (ctx.sessionId?.startsWith(SUBAGENT_PREFIX) && ctx.sessionId.length > SUBAGENT_PREFIX.length) return "subagent";
  return "main";
}

/**
 * Mirrors ZCode's attribution header helper (`createModelRequestAttributionHeaders`).
 *
 * ZCode 3.9.1 emits, in this order: x-request-id, x-zcode-session-type,
 * x-zcode-trace-id, [x-query-id], [x-session-id]. The session-type header is
 * unconditional (always resolves to main/subagent/other).
 */
export function buildZcodeTraceHeaders(ctx: ZcodeTraceContext = {}): Record<string, string> {
  const queryId = ctx.queryId ? stripHeaderInternalPrefixes(ctx.queryId, [QUERY_PREFIX]) : undefined;
  const sessionId = ctx.sessionId ? stripHeaderInternalPrefixes(ctx.sessionId, SESSION_PREFIXES) : undefined;
  return {
    "x-request-id": ctx.requestId ?? crypto.randomUUID(),
    "x-zcode-session-type": resolveSessionType(ctx),
    "x-zcode-trace-id": ctx.traceId ?? crypto.randomUUID(),
    ...(queryId ? { "x-query-id": queryId } : {}),
    ...(sessionId ? { "x-session-id": sessionId } : {}),
  };
}

export function stripHeaderInternalPrefixes(value: string, prefixes: string[]): string {
  let out = value;
  for (const prefix of prefixes) {
    if (out.startsWith(prefix) && out.length > prefix.length) out = out.slice(prefix.length);
  }
  return out || value;
}