@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
586 lines (552 loc) • 27 kB
text/typescript
import { beginOneShotEvidence, oneShotEvidenceRoot } from "./oneshot-evidence.ts";
import { defaultSpawn, type KxmOneShotProcessResult, type KxmOneShotSpawn } from "./oneshot-process.ts";
export { defaultSpawn, type KxmOneShotProcessResult, type KxmOneShotSpawn } from "./oneshot-process.ts";
import {
BUILTIN_HARNESSES,
NATIVE_HARNESS_PROVIDERS,
probeHarnessAssignmentAsync,
oneShotPermissionArgs,
type HarnessAssignmentProbeOptions,
type HarnessStatus,
type HarnessCatalogEntry,
type HarnessInventory,
type OneShotParsedOutput,
} from "./harness.ts";
import {
calculateModelCost,
findModelPrice,
loadPriceCatalog,
parsePriceCatalog,
type PriceCatalog,
} from "./prices.ts";
import {
registerTrustedProducer,
type KxmProducer,
type KxmProducerRequest,
type KxmProducerResult,
} from "./engine.ts";
export interface KxmOneShotProducerOptions {
projectRoot?: string | undefined;
evidenceRoot?: string | undefined;
defaultModel?: string | undefined;
defaultProvider?: string | undefined;
inventory?: HarnessInventory | undefined;
catalog?: readonly HarnessCatalogEntry[] | undefined;
priceCatalog?: PriceCatalog | undefined;
env?: NodeJS.ProcessEnv | undefined;
timeoutMs?: number | undefined;
spawnProcess?: KxmOneShotSpawn | undefined;
probeHarness?: ((options: Omit<HarnessAssignmentProbeOptions, "runCommand"> & { signal?: AbortSignal | undefined }) => HarnessStatus | Promise<HarnessStatus>) | undefined;
resolveModel?: ((agentId: string, runId: string) => {
provider?: string | undefined;
model?: string | undefined;
thinking?: string | undefined;
} | undefined) | undefined;
}
export interface KxmOneShotProducer extends KxmProducer {
readonly id: "oneshot";
produce(request: KxmProducerRequest): Promise<KxmProducerResult>;
close(): Promise<void>;
}
function isClosingFence(line: string): boolean {
return /^(`{3,}|~{3,})\s*$/.test(line.trim());
}
// Drop one closing code fence when it is the last line. The caller has already
// trimmed the reply. Any other tail stays, so prose after the object still fails.
function stripOneClosingFence(trimmed: string): string {
const lineBreak = Math.max(trimmed.lastIndexOf("\n"), trimmed.lastIndexOf("\r"));
const lastLine = lineBreak < 0 ? trimmed : trimmed.slice(lineBreak + 1);
if (!isClosingFence(lastLine)) return trimmed;
if (lineBreak < 0) return "";
return trimmed.slice(0, lineBreak).trimEnd();
}
// How many `}` characters, counting from the end of the reply, we will try
// as the end of an outcome object.
const MAX_OUTCOME_CLOSERS = 32;
// How many `{` openers before that closer we will consider, nearest last.
const MAX_OUTCOME_OPENERS = 32;
// A candidate whose UTF-8 size is over this is not parsed. Older openers
// for the same closer contain the nearer slice, so the opener walk stops
// there. The count is Buffer.byteLength, not JavaScript string length.
const MAX_OUTCOME_SLICE_BYTES = 256 * 1024;
// Total UTF-8 bytes of candidate slices JSON.parse may see in one lookup.
// Crossing this budget stops the lookup. Same byte measure as the slice cap.
const MAX_OUTCOME_PARSE_BYTES = 1024 * 1024;
// How many `{` / `[` openers at the end of the prefix are re-lexed. A `"`
// is not an opener. Each re-lex walks the body from that opener to the end,
// so the lexer work is linear in the body per opener checked, per candidate.
// This cap only limits how many openers are re-lexed. It does not bound the
// lexer. The slice budgets bound JSON.parse bytes, not these scans. If the
// walk reaches the cap and another container opener is still earlier in the
// prefix, the prefix is ambiguous and the candidate is rejected.
const MAX_PREFIX_CONTAINER_OPENERS = 16;
// Naive prefix scan. Quote handling toggles on every unescaped `"`. Braces
// and brackets count only outside a string. Extra closers stay at depth 0
// because they are prose, not an unclosed container. The slice itself is
// not scanned: JSON.parse owns strings and escapes there. O(prefix).
function outcomePrefixIsAnchored(prefix: string): boolean {
let objectDepth = 0;
let arrayDepth = 0;
let inString = false;
let escape = false;
for (let i = 0; i < prefix.length; i++) {
const ch = prefix[i];
if (inString) {
if (escape) {
escape = false;
continue;
}
if (ch === "\\") {
escape = true;
continue;
}
if (ch === "\"") inString = false;
continue;
}
if (ch === "\"") {
inString = true;
continue;
}
if (ch === "{") objectDepth += 1;
else if (ch === "}" && objectDepth > 0) objectDepth -= 1;
else if (ch === "[") arrayDepth += 1;
else if (ch === "]" && arrayDepth > 0) arrayDepth -= 1;
}
return objectDepth === 0 && arrayDepth === 0 && !inString && !escape;
}
// Same string, escape, and depth machine as the prefix scan, started at a
// `{` or `[`. A closer that does not match an opener this scan pushed is
// invalid. An invalid scan is not truncation. Truncation is a valid scan
// that reaches the end of the body still inside a container or a string.
// One call walks from `start` through the rest of the body. The walk is
// linear in that span. No byte budget stops it.
function containerLexIsTruncated(body: string, start: number): boolean {
const stack: string[] = [];
let inString = false;
let escape = false;
for (let i = start; i < body.length; i++) {
const ch = body[i];
if (inString) {
if (escape) {
escape = false;
continue;
}
if (ch === "\\") {
escape = true;
continue;
}
if (ch === "\"") inString = false;
continue;
}
if (ch === "\"") {
inString = true;
continue;
}
if (ch === "{" || ch === "[") {
stack.push(ch);
continue;
}
if (ch === "}" || ch === "]") {
const open = ch === "}" ? "{" : "[";
const top = stack[stack.length - 1];
if (top !== open) return false;
stack.pop();
}
}
return stack.length > 0 || inString || escape;
}
// True when one of the last container openers in the prefix lexes as a
// value that is still open at the end of the body, or when more container
// openers sit further back than the re-lex cap. Those further openers are
// not scanned. The prefix is ambiguous, so the candidate fails closed.
// Quote positions are left to the naive machine.
function prefixHidesTruncatedContainer(body: string, openAt: number): boolean {
let seen = 0;
for (let i = openAt - 1; i >= 0; i--) {
const ch = body[i];
if (ch !== "{" && ch !== "[") continue;
if (seen >= MAX_PREFIX_CONTAINER_OPENERS) return true;
seen++;
if (containerLexIsTruncated(body, i)) return true;
}
return false;
}
// The outcome object is a JSON object that ends the trimmed reply. One
// closing code fence may follow it; the fence is removed before the walk.
// Candidates are slices from a `{` to a recent `}`, nearest last. Only the
// last 32 closers are visited, and only a closer on the reply's last
// character can end the reply. For that closer the last 32 openers are
// tried. A slice whose UTF-8 size is over 256 KiB is skipped. Parsed slices
// share a 1 MiB UTF-8 budget, and crossing it stops the lookup. Both limits
// use Buffer.byteLength. JSON.parse is the only
// string and escape authority inside the slice. A slice that parses to a
// plain object whose outcome is a string in allowedOutcomes is a candidate.
// The prefix before that opener, with trailing whitespace removed, must end
// at depth 0 for both braces and arrays, outside a string, with no dangling
// escape. Any other end state is truncated or ambiguous, so the candidate
// is rejected and the next one is tried. The naive machine plus the
// container-lexer guard reject truncation. A prefix with more container
// openers than the re-lex cap is ambiguous and fails closed. Prose with an
// odd quote count fails closed. An accepted candidate ends the lookup.
// Nothing left is failed: prose after the object, no object, a disallowed
// outcome, an inner object at the end of a truncated outer object or array,
// an object inside an unclosed string, an ambiguous prefix past the re-lex
// cap, or a budget exhausted before a candidate.
function determineOutcome(text: string, allowedOutcomes: readonly string[]): string {
const trimmed = text.trim();
if (!trimmed) return "failed";
const body = stripOneClosingFence(trimmed);
if (!body) return "failed";
let seenClosers = 0;
let parseBytes = 0;
for (let closeAt = body.length - 1; closeAt >= 0 && seenClosers < MAX_OUTCOME_CLOSERS; closeAt--) {
if (body[closeAt] !== "}") continue;
seenClosers++;
const end = closeAt + 1;
// A candidate has to end the reply. One closer can sit at that position.
if (end !== body.length) continue;
let seenOpeners = 0;
for (let openAt = closeAt - 1; openAt >= 0 && seenOpeners < MAX_OUTCOME_OPENERS; openAt--) {
if (body[openAt] !== "{") continue;
seenOpeners++;
const slice = body.slice(openAt, end);
const sliceBytes = Buffer.byteLength(slice, "utf8");
if (sliceBytes > MAX_OUTCOME_SLICE_BYTES) break;
if (parseBytes + sliceBytes > MAX_OUTCOME_PARSE_BYTES) return "failed";
parseBytes += sliceBytes;
let result: unknown;
try {
result = JSON.parse(slice);
} catch {
continue;
}
if (!result || typeof result !== "object" || Array.isArray(result)) continue;
const outcome = (result as Record<string, unknown>).outcome;
if (typeof outcome !== "string" || !allowedOutcomes.includes(outcome)) continue;
const prefix = body.slice(0, openAt).trimEnd();
if (!outcomePrefixIsAnchored(prefix)) continue;
if (prefixHidesTruncatedContainer(body, openAt)) continue;
return outcome;
}
}
return "failed";
}
export function createKxmOneShotProducer(options: KxmOneShotProducerOptions = {}): KxmOneShotProducer {
const running = new Map<AbortController, Promise<KxmProducerResult>>();
let closed = false;
function requireHarness(request: KxmProducerRequest): string {
if (request.harness) return request.harness;
throw new Error("producer_harness_required");
}
function parseModelString(spec: string, harness: string): { provider: string; model: string } {
const trimmed = spec.trim();
if (trimmed.includes("/")) {
const idx = trimmed.indexOf("/");
return { provider: trimmed.slice(0, idx).toLowerCase(), model: trimmed.slice(idx + 1) };
}
const fallbackProvider = NATIVE_HARNESS_PROVIDERS[harness] ?? options.defaultProvider ?? "anthropic";
return { provider: fallbackProvider, model: trimmed };
}
function resolveModelForRequest(
request: KxmProducerRequest,
harness: string,
): { provider: string; model: string; thinking?: string | undefined } {
if (request.model) {
// A separate provider field means the model id is bare and may itself be
// namespaced (openrouter + qwen/qwen3.7-plus); reparsing it as a
// provider/model selector strips the namespace — the same defect #262 fixed
// in the pi producer. Only parse when the provider must come from the string.
if (request.provider) {
return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
}
const parsed = parseModelString(request.model, harness);
return {
provider: request.provider?.toLowerCase() ?? parsed.provider,
model: parsed.model,
thinking: request.thinking,
};
}
if (options.resolveModel) {
const resolved = options.resolveModel(request.agentId, request.runId);
if (resolved && resolved.model) {
if (resolved.provider) {
return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
}
const parsed = parseModelString(resolved.model, harness);
return {
provider: resolved.provider?.toLowerCase() ?? parsed.provider,
model: parsed.model,
thinking: resolved.thinking,
};
}
}
if (options.defaultModel) {
const parsed = parseModelString(options.defaultModel, harness);
return { provider: parsed.provider, model: parsed.model, thinking: request.thinking };
}
throw new Error("producer_route_not_admitted");
}
async function checkAuth(harness: string, provider: string, model: string, env: NodeJS.ProcessEnv, signal: AbortSignal): Promise<HarnessStatus> {
if (options.inventory) {
const entry = options.inventory.harnesses.find((h) => h.id === harness);
if (!entry || !entry.detected || entry.authenticated !== true) {
throw new Error(`${harness}_not_authenticated: ${harness} harness not authenticated in inventory`);
}
}
const probeFn = options.probeHarness ?? probeHarnessAssignmentAsync;
const probe = await probeFn({ harness, provider, model, env, signal, timeoutMs: 10_000 });
if (!probe.detected) {
throw new Error(`${harness}_not_authenticated: ${harness} harness not detected (${probe.issues.join(", ")})`);
}
if (probe.authenticated !== true) {
throw new Error(`${harness}_not_authenticated: ${harness} not authenticated (${probe.issues.join(", ")})`);
}
return probe;
}
async function executeRequest(request: KxmProducerRequest): Promise<KxmProducerResult> {
const startTime = Date.now();
const harness = requireHarness(request);
const resolved = resolveModelForRequest(request, harness);
if (closed) throw new Error("oneshot_producer_closed");
const cancelled = (): KxmProducerResult => ({ outcome: "cancelled", costBasis: "unknown", costUsd: null,
latencyMs: Math.max(0, Date.now() - startTime), harness, provider: resolved.provider,
requestedModel: resolved.model, effectiveModel: "unknown", agentRole: request.agentRole ?? request.agentId });
if (request.signal.aborted) return cancelled();
const catalogEntry = (options.catalog ?? BUILTIN_HARNESSES).find((h) => h.id === harness);
if (!catalogEntry?.oneShot) throw new Error(`oneshot_harness_unsupported: ${harness}`);
const permission = request.permission === "edit" || request.contextPacket?.task.permissionCeiling === "edit"
? "edit"
: "read-only";
const permissionArgs = oneShotPermissionArgs(harness, permission);
if (!permissionArgs) {
throw new Error(`oneshot_harness_unsupported: ${harness} ${permission === "edit" ? "writer_profile_unaudited" : "permission_profile_unaudited"}`);
}
// Pin the environment for auth and execution; don't observe subscription
// auth under one environment and then spawn under changed API-key settings.
const env = { ...(options.env ?? process.env) };
let auth: HarnessStatus;
try { auth = await checkAuth(harness, resolved.provider, resolved.model, env, request.signal); }
catch (error) { if (request.signal.aborted) return cancelled(); throw error; }
if (request.signal.aborted) return cancelled();
const command = auth.command ?? catalogEntry.commands[0] ?? harness;
const oneShot = catalogEntry.oneShot;
let args: string[];
if (harness === "claude") {
args = [
"-p",
"--model",
resolved.model,
...(resolved.thinking ? ["--effort", resolved.thinking] : []),
...permissionArgs,
"--output-format",
"json",
];
} else if (harness === "codex") {
args = [
"exec",
"-m",
resolved.model,
...(resolved.thinking ? ["-c", `model_reasoning_effort="${resolved.thinking}"`] : []),
...permissionArgs,
"--json",
"-",
];
} else if (harness === "grok") {
args = ["--model", resolved.model,
...(resolved.thinking ? ["--reasoning-effort", resolved.thinking] : []),
...permissionArgs, "--output-format", "json"];
} else if (harness === "agy") {
args = [
"-m",
resolved.model,
...permissionArgs,
"--output-format",
"json",
"-p",
];
} else if (harness === "kimi") {
args = [
"-m",
resolved.model,
...permissionArgs,
"--output-format",
"stream-json",
"-p",
];
} else if (harness === "pi") {
// pi's model flag takes the provider-qualified id — exactly the auth probe's
// rule (#265): prefix with the provider unless the model already starts with it.
// A bare "contains a slash" test is wrong for provider-local namespaces
// (openrouter + qwen/qwen3.7-plus must spawn as the full three-part id).
const qualified = resolved.provider && resolved.model.startsWith(`${resolved.provider}/`)
? resolved.model
: resolved.provider ? `${resolved.provider}/${resolved.model}` : resolved.model;
args = [
"--model",
qualified,
...permissionArgs,
...oneShot.argv,
];
} else {
throw new Error(`oneshot_harness_unsupported: ${harness} permission_profile_unaudited`);
}
const taskPrompt = request.prompt
?? `Execute step ${request.stepId} (attempt ${request.stepAttempt}) for agent ${request.agentId}.`;
const promptMessage = `${taskPrompt}\n\nReturn a final JSON object with an "outcome" field chosen from ${JSON.stringify(request.allowedOutcomes)} and a concise "summary". Do not wrap it in Markdown. If no allowed outcome is truthful, return {"outcome":"failed"}; never infer success from an incomplete task.`;
let input: string | undefined;
if (oneShot.promptVia === "stdin") {
input = promptMessage;
} else {
if (harness === "grok") args.push("--single");
args.push(promptMessage);
}
const spawnFn = options.spawnProcess ?? defaultSpawn;
const cwd = options.projectRoot ?? process.cwd();
const timeoutMs = request.timeoutMs ?? options.timeoutMs;
const evidence = await beginOneShotEvidence(options.evidenceRoot ?? oneShotEvidenceRoot(), {
runId: request.runId, stepId: request.stepId, attemptId: request.attemptId, assignmentId: request.assignmentId,
harness, provider: resolved.provider, model: resolved.model, cwd, command, args, input,
timeoutMs: timeoutMs ?? 120_000,
}, [request.capability, ...Object.entries(env).filter(([key]) => /TOKEN|KEY|SECRET|PASSWORD|COOKIE|AUTH/i.test(key)).map(([, value]) => value ?? "")]);
let procResult: KxmOneShotProcessResult;
try {
procResult = request.signal.aborted
? { stdout: "", stderr: "", code: null, started: false, observedChildExit: false, error: new Error("process_aborted") }
: await spawnFn(command, args, { cwd, env, input, timeoutMs, signal: request.signal });
} catch (error) {
// A rejected adapter promise is not proof that no process/effect started.
procResult = { stdout: "", stderr: "", code: null, observedChildExit: false, error: error instanceof Error ? error : new Error("process_adapter_error") };
}
// Parse available usage even when execution failed or was interrupted.
let parsed: OneShotParsedOutput;
try { parsed = oneShot.usageParser(procResult.stdout, procResult.stderr, resolved.model); }
catch { parsed = { text: "", isError: true }; }
const aborted = request.signal.aborted || procResult.error?.message === "process_aborted";
// Process groups are cleanup, not containment. A detached descendant or
// provider leader may outlive the client; interrupted effects need recovery.
const unverifiedDescendants = procResult.unverifiedDescendants === true
|| procResult.terminationRequested === true
|| procResult.error?.message === "process_exit_unobserved"
|| (procResult.observedChildExit === false && procResult.started !== false);
const effectUncertain = unverifiedDescendants || Boolean(procResult.signal);
const transportFailed = procResult.code !== 0 || Boolean(procResult.error) || effectUncertain;
const outcome = aborted ? "cancelled" : transportFailed || parsed.isError ? "failed"
: determineOutcome(parsed.text, request.allowedOutcomes);
const providerMetadata: Record<string, string | number | boolean> = {
processStatus: aborted ? "aborted" : transportFailed ? "failed" : "completed",
executionEvidenceId: evidence.id,
permission,
permissionProfile: permission === "edit" ? "writer" : "read-only",
authored: false,
};
if (procResult.code !== null && Number.isFinite(procResult.code)) providerMetadata.processExitCode = procResult.code;
if (procResult.signal) providerMetadata.processSignal = procResult.signal;
if (procResult.observedChildExit !== undefined) providerMetadata.observedChildExit = procResult.observedChildExit;
if (procResult.started !== undefined) providerMetadata.processStarted = procResult.started;
if (procResult.terminationRequested !== undefined) providerMetadata.terminationRequested = procResult.terminationRequested;
if (unverifiedDescendants) providerMetadata.descendantEffects = "unverified";
if (procResult.error) {
const reason = procResult.error.message;
providerMetadata.processError = /^process_(aborted|timeout|output_limit|stdin_error|stdio_error|stdio_unclosed|exit_unobserved)$/.test(reason) ? reason : "process_error";
}
const latencyMs = Math.max(0, Date.now() - startTime);
const usage = parsed.usage;
const validCount = (value: unknown): value is number => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
const boundedCount = (field: string, value: unknown): number | null => {
if (validCount(value) && value <= 1_000_000) return value;
if (typeof value === "number" && Number.isFinite(value)) {
providerMetadata[`raw${field[0]!.toUpperCase()}${field.slice(1)}`] = value;
}
return null;
};
const tokensIn = boundedCount("tokensIn", usage?.tokensIn);
const tokensOut = boundedCount("tokensOut", usage?.tokensOut);
const cacheReadTokens = boundedCount("cacheReadTokens", usage?.cacheReadTokens);
const cacheWriteTokens = boundedCount("cacheWriteTokens", usage?.cacheWriteTokens);
const contextTokens = boundedCount("contextTokens", usage?.contextTokens);
if (typeof usage?.costUsd === "number" && Number.isFinite(usage.costUsd) && usage.costUsd >= 0) providerMetadata.providerReportedCostUsd = usage.costUsd;
// List estimates and provider-reported amounts are not proof of a bill.
let catalog: PriceCatalog | undefined;
try {
catalog = options.priceCatalog ? parsePriceCatalog(JSON.stringify(options.priceCatalog))
: loadPriceCatalog(options.projectRoot ?? process.cwd());
} catch { providerMetadata.priceCatalogUnavailable = true; }
// Without a freshness-aware vendor feed, don't silently quote an older
// local snapshot. Even today's hash-verified snapshot is only an estimate.
if (catalog && catalog.date !== new Date().toISOString().slice(0, 10)) {
providerMetadata.priceCatalogStale = true;
catalog = undefined;
}
const subscription = ["claude.ai", "ChatGPT", "antigravity-oauth"].includes(auth.authMethod ?? "");
const costBasis = subscription ? "unmetered" as const : "unknown" as const;
const costUsd = null;
const priceRef = subscription ? `subscription:${harness}` : undefined;
if (auth.authMethod) providerMetadata.authMethod = auth.authMethod;
const priceRow = catalog ? findModelPrice(catalog, resolved.model, resolved.provider) : undefined;
const flatTier = priceRow?.tiers.length === 1 && priceRow.tiers[0]?.upToContextTokens == null ? priceRow.tiers[0] : undefined;
// Unknown context occupancy cannot select a context tier. Claude reports
// uncached input separately; other protocols need their own price semantics.
if (catalog && harness === "claude" && flatTier
&& validCount(usage?.tokensIn) && validCount(usage?.tokensOut)
&& validCount(usage?.cacheReadTokens) && validCount(usage?.cacheWriteTokens)
&& (usage.cacheReadTokens === 0 || flatTier.cacheReadPerMillion != null)
&& (usage.cacheWriteTokens === 0 || flatTier.cacheWritePerMillion != null)) {
const calculated = calculateModelCost(catalog, {
model: resolved.model, provider: resolved.provider,
tokensIn: usage.tokensIn, tokensOut: usage.tokensOut,
cacheReadTokens: usage.cacheReadTokens, cacheWriteTokens: usage.cacheWriteTokens,
contextTokens,
});
if (calculated && Number.isFinite(calculated.costUsd)) {
providerMetadata.listCostUsd = calculated.costUsd;
providerMetadata.listPriceRef = calculated.priceRef;
providerMetadata.listPriceSha256 = catalog.sha256;
}
}
const result: KxmProducerResult = {
outcome,
costBasis,
costUsd,
tokensIn,
tokensOut,
cacheReadTokens,
cacheWriteTokens,
contextTokens,
latencyMs,
harness,
provider: resolved.provider,
requestedModel: resolved.model,
effectiveModel: parsed.effectiveModel ?? "unknown",
providerMetadata,
...(effectUncertain ? { effectUncertain: true as const } : {}),
...(resolved.thinking !== undefined ? { thinking: resolved.thinking } : {}),
agentRole: request.agentRole ?? request.agentId,
...(priceRef !== undefined ? { priceRef } : {}),
};
try { providerMetadata.executionEvidenceSha256 = await evidence.finish(procResult, result); }
catch {
providerMetadata.evidenceWriteFailed = true;
return { ...result, outcome: "failed", effectUncertain: true };
}
return result;
}
const producer: KxmOneShotProducer = {
id: "oneshot",
produce(request): Promise<KxmProducerResult> {
const controller = new AbortController();
const signal = AbortSignal.any([request.signal, controller.signal]);
const pending = Promise.resolve().then(() => executeRequest({ ...request, signal }))
.finally(() => { running.delete(controller); });
running.set(controller, pending);
return pending;
},
async close(): Promise<void> {
closed = true;
const pending = [...running.values()];
for (const controller of running.keys()) controller.abort();
await Promise.allSettled(pending);
},
};
registerTrustedProducer(producer);
return producer;
}