Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const REQUESTER_RUNTIME_SCHEMA_VERSION = "2026-06-04"
const REQUESTER_RUNTIME_SOURCE = "openclaw-taas-affinity"
const GIT_PROBE_TIMEOUT_MS = 250
const LAST_ROUTE_LIMIT = 256
const PLUGIN_VERSION = "0.6.0"
const PLUGIN_VERSION = "0.7.0"

// OpenClaw stores active registry state (including workspaceDir) on globalThis
// under this well-known symbol key.
Expand Down Expand Up @@ -180,6 +180,34 @@ function deriveAgentIdForCapture(ctx: { agentDir?: string; workspaceDir?: string
return seg
}

/**
* Agent identity for the correlation envelope.
*
* TaaS requires BOTH an agent id and a session id before it will trust a
* caller-supplied identity (`identity_present = session_present && agent_present`).
* When neither is usable it discards the identity and mints a fresh session per
* request, which silently destroys affinity and prompt-cache reuse.
*
* Directory/env derivation is best-effort and frequently unavailable, so fall
* back to the agent segment encoded in OpenClaw session keys
* (`agent:<agentId>:<scope>`) and finally to a stable literal. The value only
* needs to be stable for the conversation, never globally unique.
*/
function resolveAgentIdentity(
ctx: { agentDir?: string; workspaceDir?: string },
sessionId: string | null | undefined
): string | null {
const derived = deriveAgentIdForCapture(ctx)
if (derived) return derived
const sid = safeString(sessionId)
if (sid) {
const match = /^agent:([^:]+):/.exec(sid)
if (match?.[1]) return match[1]
return "main"
}
return null
}

function buildCorrelationMetadata(
sessionId: string,
source: string,
Expand Down Expand Up @@ -365,7 +393,12 @@ function buildWrapper(ctx: ProviderWrapStreamFnContext) {
if (!streamFn) return undefined

const identity = resolveSessionIdentity(ctx.workspaceDir, (ctx as { sessionId?: unknown }).sessionId)
const agentIdForCapture = deriveAgentIdForCapture(ctx as { agentDir?: string; workspaceDir?: string })
// Resolve agent identity from the session key when no dir/env hint exists so
// TaaS never falls back to minting its own per-request session id.
const agentIdForCapture = resolveAgentIdentity(
ctx as { agentDir?: string; workspaceDir?: string },
identity?.sessionId
)
const requesterRuntime = identity ? buildRequesterRuntime(ctx, identity.sessionId, identity.source, identity.sourceHint) : undefined
const correlation = identity ? buildCorrelationMetadata(identity.sessionId, identity.source, identity.sourceHint, ctx, agentIdForCapture) : undefined

Expand Down Expand Up @@ -402,7 +435,10 @@ function buildTransportTurnState(ctx: ProviderResolveTransportTurnStateContext):
if (isDev) console.debug(`[taas-affinity] no native or legacy session identity; skipping affinity headers turnId=${ctx.turnId}`)
return null
}
const agentId = deriveAgentIdForCapture(ctx as unknown as { agentDir?: string; workspaceDir?: string })
const agentId = resolveAgentIdentity(
ctx as unknown as { agentDir?: string; workspaceDir?: string },
identity.sessionId
)
if (isDev) {
console.debug(
`[taas-affinity] resolveTransportTurnState sessionId=${identity.sessionId} ` +
Expand All @@ -413,6 +449,14 @@ function buildTransportTurnState(ctx: ProviderResolveTransportTurnStateContext):
}

export default {
// Internal helpers exposed strictly for unit tests. Not part of the plugin
// contract and not used at runtime.
__test__: {
resolveAgentIdentity,
deriveAgentIdForCapture,
buildCorrelationMetadata,
buildCorrelationHeaders,
},
id: "openclaw-taas-affinity",
name: "CloudSigma TaaS Token Cache Optimizer",
description:
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "openclaw-taas-affinity",
"version": "0.6.0",
"description": "OpenClaw provider plugin CloudSigma TaaS session affinity. Injects a stable X-Session-Id header per conversation so TaaS can pin the session to the same OAuth token / Bedrock region / Claude Code node, maximising prompt-cache hit rates.",
"version": "0.7.0",
"description": "OpenClaw provider plugin \u2014 CloudSigma TaaS session affinity. Injects a stable X-Session-Id header per conversation so TaaS can pin the session to the same OAuth token / Bedrock region / Claude Code node, maximising prompt-cache hit rates.",
"type": "module",
"main": "dist/index.js",
"openclaw": {
Expand Down
83 changes: 83 additions & 0 deletions test/agent-identity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import assert from "node:assert/strict"
import test from "node:test"

import plugin from "../index.ts"

/**
* TaaS requires BOTH an agent id and a session id before it trusts a
* caller-supplied identity:
*
* identity_present = session_present && agent_present
*
* In production every request logged
* `fallback_reason=missing_agent_and_session`, so TaaS discarded the supplied
* identity and minted a fresh session per request. That destroyed affinity and
* pinned continuity at 0%.
*/

const internals = (plugin as unknown as { __test__?: Record<string, unknown> }).__test__

test("agent identity is derived from an OpenClaw session key when no dir hint exists", () => {
assert.ok(internals, "plugin must expose test internals")
const resolveAgentIdentity = internals!.resolveAgentIdentity as (
ctx: { agentDir?: string; workspaceDir?: string },
sessionId: string | null | undefined
) => string | null

// No agentDir/workspaceDir, which is the production case for the
// openai-completions transport.
assert.equal(resolveAgentIdentity({}, "agent:main:main"), "main")
assert.equal(resolveAgentIdentity({}, "agent:new-agent-3:main"), "new-agent-3")
assert.equal(
resolveAgentIdentity({}, "agent:main:subagent:1ab99fc0-55fb-4b22-a5b3-c4223ef63d6d"),
"main"
)
})

test("agent identity still prefers an explicit directory hint", () => {
const resolveAgentIdentity = internals!.resolveAgentIdentity as (
ctx: { agentDir?: string; workspaceDir?: string },
sessionId: string | null | undefined
) => string | null

assert.equal(resolveAgentIdentity({ workspaceDir: "/home/x/workspace" }, "agent:zzz:main"), "main")
assert.equal(
resolveAgentIdentity({ workspaceDir: "/home/x/workspace-billing" }, "agent:zzz:main"),
"billing"
)
})

test("no session identity yields no fabricated agent identity", () => {
const resolveAgentIdentity = internals!.resolveAgentIdentity as (
ctx: { agentDir?: string; workspaceDir?: string },
sessionId: string | null | undefined
) => string | null

assert.equal(resolveAgentIdentity({}, null), null)
assert.equal(resolveAgentIdentity({}, undefined), null)
assert.equal(resolveAgentIdentity({}, ""), null)
})

test("correlation envelope carries agent_id so TaaS sees a complete identity", () => {
const buildCorrelationMetadata = internals!.buildCorrelationMetadata as (
sessionId: string,
source: string,
sourceHint: string,
ctx: unknown,
agentId: string | null
) => Record<string, unknown>

const meta = buildCorrelationMetadata(
"agent:main:main",
"openclaw:ctx.sessionId",
"stateDir:/home/x/.openclaw",
{ provider: "cloudsigma", modelId: "gpt-5.6-sol" },
"main"
)

assert.equal(meta.session_id, "agent:main:main")
assert.equal(meta.agent_id, "main")
// TaaS classifies declared_plugin from a source starting with "openclaw-".
assert.ok(String(meta.source).startsWith("openclaw-"))
assert.ok(meta.plugin_version)
})
4 changes: 2 additions & 2 deletions test/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const transportState = provider.resolveTransportTurnState({ sessionId: "smoke-lo

assert.equal(transportState.headers["X-Session-Id"], "smoke-local-session")
assert.equal(transportState.headers["X-OpenClaw-Session-Id"], transportState.headers["X-Session-Id"])
assert.equal(transportState.headers["X-OpenClaw-Plugin-Version"], "0.6.0")
assert.equal(transportState.headers["X-OpenClaw-Plugin-Version"], "0.7.0")
assert.equal(transportState.headers["X-OpenClaw-Turn-Id"], "turn-smoke")
assert.equal(transportState.headers["X-OpenClaw-Attempt"], "1")

Expand All @@ -93,7 +93,7 @@ assert.equal(capturedPayload.metadata.requester_runtime.tool_execution, "directi
assert.equal("available_bridges" in capturedPayload.metadata.requester_runtime, false)
assert.equal(capturedPayload.metadata.openclaw_correlation.schema_version, "2026-06-05")
assert.equal(capturedPayload.metadata.openclaw_correlation.source, "openclaw-taas-affinity")
assert.equal(capturedPayload.metadata.openclaw_correlation.plugin_version, "0.6.0")
assert.equal(capturedPayload.metadata.openclaw_correlation.plugin_version, "0.7.0")
assert.equal(capturedPayload.metadata.openclaw_correlation.session_id, localSessionA)
assert.equal(capturedPayload.metadata.openclaw_correlation.sticky_key, localSessionA)
assert.equal(capturedPayload.metadata.openclaw_correlation.provider, "cloudsigma")
Expand Down
Loading