@mcoda/agents 0.1.36 → 0.1.38

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.
@@ -1 +1 @@
1
- {"version":3,"file":"CodexAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtG,qBAAa,YAAa,YAAW,YAAY;IACnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEnC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAYnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmB5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;CAoBjG"}
1
+ {"version":3,"file":"CodexAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAmBtG,qBAAa,YAAa,YAAW,YAAY;IACnC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEnC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAYnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwB5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;CAyBjG"}
@@ -1,4 +1,18 @@
1
1
  import { cliHealthy, runCodexExec, runCodexExecStream } from "./CodexCliRunner.js";
2
+ const extractOutputSchema = (request) => {
3
+ const candidate = request.metadata?.outputSchema;
4
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
5
+ return undefined;
6
+ }
7
+ return candidate;
8
+ };
9
+ const extractTimeoutMs = (request) => {
10
+ const candidate = request.metadata?.timeoutMs;
11
+ if (typeof candidate !== "number" || !Number.isFinite(candidate) || candidate <= 0) {
12
+ return undefined;
13
+ }
14
+ return Math.floor(candidate);
15
+ };
2
16
  export class CodexAdapter {
3
17
  constructor(config) {
4
18
  this.config = config;
@@ -20,7 +34,7 @@ export class CodexAdapter {
20
34
  async invoke(request) {
21
35
  const health = cliHealthy(true);
22
36
  const cliDetails = health.details;
23
- const result = await runCodexExec(request.input, this.config.model);
37
+ const result = await runCodexExec(request.input, this.config.model, extractOutputSchema(request), extractTimeoutMs(request));
24
38
  return {
25
39
  output: result.output,
26
40
  adapter: this.config.adapter ?? "codex-cli",
@@ -38,7 +52,7 @@ export class CodexAdapter {
38
52
  async *invokeStream(request) {
39
53
  const health = cliHealthy(true);
40
54
  const cliDetails = health.details;
41
- for await (const chunk of runCodexExecStream(request.input, this.config.model)) {
55
+ for await (const chunk of runCodexExecStream(request.input, this.config.model, extractOutputSchema(request), extractTimeoutMs(request))) {
42
56
  yield {
43
57
  output: chunk.output,
44
58
  adapter: this.config.adapter ?? "codex-cli",
@@ -2,11 +2,11 @@ export declare const cliHealthy: (throwOnError?: boolean) => {
2
2
  ok: boolean;
3
3
  details?: Record<string, unknown>;
4
4
  };
5
- export declare const runCodexExec: (prompt: string, model?: string) => Promise<{
5
+ export declare const runCodexExec: (prompt: string, model?: string, outputSchema?: Record<string, unknown>, timeoutMs?: number) => Promise<{
6
6
  output: string;
7
7
  raw: string;
8
8
  }>;
9
- export declare function runCodexExecStream(prompt: string, model?: string): AsyncGenerator<{
9
+ export declare function runCodexExecStream(prompt: string, model?: string, outputSchema?: Record<string, unknown>, timeoutMs?: number): AsyncGenerator<{
10
10
  output: string;
11
11
  raw: string;
12
12
  }, void, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"CodexCliRunner.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexCliRunner.ts"],"names":[],"mappings":"AAuyBA,eAAO,MAAM,UAAU,GAAI,sBAAoB,KAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CA+BjG,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,QAAQ,MAAM,EAAE,QAAQ,MAAM,KAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAS1G,CAAC;AAEF,wBAAuB,kBAAkB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,cAAc,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAgEhE"}
1
+ {"version":3,"file":"CodexCliRunner.d.ts","sourceRoot":"","sources":["../../../src/adapters/codex/CodexCliRunner.ts"],"names":[],"mappings":"AAk1BA,eAAO,MAAM,UAAU,GAAI,sBAAoB,KAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CA+BjG,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,QAAQ,MAAM,EACd,QAAQ,MAAM,EACd,eAAe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,YAAY,MAAM,KACjB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CASzC,CAAC;AAEF,wBAAuB,kBAAkB,CACvC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,SAAS,CAAC,EAAE,MAAM,GACjB,cAAc,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAmFhE"}
@@ -1,4 +1,7 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
2
5
  const CODEX_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
3
6
  const CODEX_REASONING_ENV = "MCODA_CODEX_REASONING_EFFORT";
4
7
  const CODEX_REASONING_ENV_FALLBACK = "CODEX_REASONING_EFFORT";
@@ -208,6 +211,7 @@ const parseCodexOutput = (raw) => {
208
211
  }
209
212
  return message;
210
213
  };
214
+ const normalizeComparableAssistantText = (value) => value.replace(/\r\n/g, "\n").trim();
211
215
  const normalizeValue = (value) => {
212
216
  if (typeof value !== "string")
213
217
  return value;
@@ -635,12 +639,35 @@ const extractAssistantText = (parsed) => {
635
639
  }
636
640
  return null;
637
641
  };
638
- const invokeCodexProcess = async (prompt, model, hooks) => {
642
+ const materializeOutputSchema = async (outputSchema) => {
643
+ if (!outputSchema || typeof outputSchema !== "object" || Array.isArray(outputSchema)) {
644
+ return undefined;
645
+ }
646
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), "mcoda-codex-schema-"));
647
+ const schemaPath = path.join(tempDir, "output-schema.json");
648
+ await writeFile(schemaPath, `${JSON.stringify(outputSchema, null, 2)}\n`, "utf8");
649
+ return {
650
+ schemaPath,
651
+ cleanup: async () => {
652
+ try {
653
+ await rm(tempDir, { recursive: true, force: true });
654
+ }
655
+ catch {
656
+ /* ignore cleanup errors */
657
+ }
658
+ },
659
+ };
660
+ };
661
+ const invokeCodexProcess = async (prompt, model, options) => {
639
662
  const resolvedModel = model ?? "gpt-5.1-codex-max";
640
663
  const sandboxArgs = resolveSandboxArgs();
641
664
  const reasoningEffort = resolveReasoningEffort(resolvedModel);
642
665
  const codexCommand = resolveCodexCommand();
643
666
  const args = [...codexCommand.preArgs, ...sandboxArgs.args, "exec", "--model", resolvedModel, "--json"];
667
+ const schemaFile = await materializeOutputSchema(options?.outputSchema);
668
+ if (schemaFile) {
669
+ args.push("--output-schema", schemaFile.schemaPath);
670
+ }
644
671
  if (!sandboxArgs.bypass) {
645
672
  args.push("--full-auto");
646
673
  }
@@ -648,141 +675,148 @@ const invokeCodexProcess = async (prompt, model, hooks) => {
648
675
  args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
649
676
  }
650
677
  args.push("-");
651
- const timeoutMs = resolveCodexTimeoutMs();
678
+ const timeoutMs = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs) && options.timeoutMs > 0
679
+ ? Math.floor(options.timeoutMs)
680
+ : resolveCodexTimeoutMs();
652
681
  const exitGraceMs = resolveCodexExitGraceMs();
653
- return await new Promise((resolve, reject) => {
654
- const child = spawn(codexCommand.command, args, { stdio: ["pipe", "pipe", "pipe"] });
655
- let raw = "";
656
- let stderr = "";
657
- let lineBuffer = "";
658
- let message = "";
659
- let protocolError;
660
- let settled = false;
661
- let forcedExit = false;
662
- let completionTimer;
663
- const clearTimers = () => {
664
- if (completionTimer) {
665
- clearTimeout(completionTimer);
666
- completionTimer = undefined;
667
- }
668
- clearTimeout(timeoutHandle);
669
- };
670
- const finalizeOutput = () => {
671
- const parsedOutput = parseCodexOutput(raw).trim();
672
- return parsedOutput || message.trim();
673
- };
674
- const finishResolve = () => {
675
- if (settled)
676
- return;
677
- settled = true;
678
- clearTimers();
679
- resolve({ output: finalizeOutput(), raw, forcedExit });
680
- };
681
- const finishReject = (error) => {
682
- if (settled)
683
- return;
684
- settled = true;
685
- clearTimers();
686
- reject(error);
687
- };
688
- const scheduleCompletionGrace = () => {
689
- if (settled)
690
- return;
691
- if (completionTimer) {
692
- clearTimeout(completionTimer);
693
- }
694
- completionTimer = setTimeout(() => {
682
+ try {
683
+ return await new Promise((resolve, reject) => {
684
+ const child = spawn(codexCommand.command, args, { stdio: ["pipe", "pipe", "pipe"] });
685
+ let raw = "";
686
+ let stderr = "";
687
+ let lineBuffer = "";
688
+ let message = "";
689
+ let protocolError;
690
+ let settled = false;
691
+ let forcedExit = false;
692
+ let completionTimer;
693
+ const clearTimers = () => {
694
+ if (completionTimer) {
695
+ clearTimeout(completionTimer);
696
+ completionTimer = undefined;
697
+ }
698
+ clearTimeout(timeoutHandle);
699
+ };
700
+ const finalizeOutput = () => {
701
+ const parsedOutput = parseCodexOutput(raw).trim();
702
+ return parsedOutput || message.trim();
703
+ };
704
+ const finishResolve = () => {
705
+ if (settled)
706
+ return;
707
+ settled = true;
708
+ clearTimers();
709
+ resolve({ output: finalizeOutput(), raw, forcedExit });
710
+ };
711
+ const finishReject = (error) => {
712
+ if (settled)
713
+ return;
714
+ settled = true;
715
+ clearTimers();
716
+ reject(error);
717
+ };
718
+ const scheduleCompletionGrace = () => {
719
+ if (settled)
720
+ return;
721
+ if (completionTimer) {
722
+ clearTimeout(completionTimer);
723
+ }
724
+ completionTimer = setTimeout(() => {
725
+ if (settled)
726
+ return;
727
+ forcedExit = true;
728
+ safeKill(child, "SIGTERM");
729
+ scheduleHardKill(child);
730
+ finishResolve();
731
+ }, exitGraceMs);
732
+ completionTimer.unref();
733
+ };
734
+ const handleLine = (line) => {
735
+ const normalized = line.replace(/\r$/, "");
736
+ options?.hooks?.onLine?.(normalized);
737
+ const parsed = safeJsonParse(normalized);
738
+ const event = extractAssistantText(parsed);
739
+ if (event) {
740
+ if (event.kind === "delta") {
741
+ message += event.text;
742
+ }
743
+ else {
744
+ message = event.text;
745
+ scheduleCompletionGrace();
746
+ }
747
+ options?.hooks?.onAssistantEvent?.(event, normalized);
748
+ }
749
+ const parsedError = extractProtocolError(parsed);
750
+ if (parsedError) {
751
+ protocolError = parsedError;
752
+ }
753
+ if (isCompletionEvent(parsed)) {
754
+ scheduleCompletionGrace();
755
+ }
756
+ };
757
+ const timeoutHandle = setTimeout(() => {
695
758
  if (settled)
696
759
  return;
697
760
  forcedExit = true;
698
761
  safeKill(child, "SIGTERM");
699
762
  scheduleHardKill(child);
700
- finishResolve();
701
- }, exitGraceMs);
702
- completionTimer.unref();
703
- };
704
- const handleLine = (line) => {
705
- const normalized = line.replace(/\r$/, "");
706
- hooks?.onLine?.(normalized);
707
- const parsed = safeJsonParse(normalized);
708
- const event = extractAssistantText(parsed);
709
- if (event) {
710
- if (event.kind === "delta") {
711
- message += event.text;
763
+ const detail = stderr.trim().length > 0 ? `: ${stderr.trim()}` : "";
764
+ finishReject(new Error(`AUTH_ERROR: codex CLI timed out after ${timeoutMs}ms${detail}`));
765
+ }, timeoutMs);
766
+ timeoutHandle.unref();
767
+ child.stdout?.setEncoding("utf8");
768
+ child.stdout?.on("data", (chunk) => {
769
+ const text = chunk.toString();
770
+ raw += text;
771
+ lineBuffer += text;
772
+ let idx;
773
+ while ((idx = lineBuffer.indexOf("\n")) !== -1) {
774
+ const line = lineBuffer.slice(0, idx);
775
+ lineBuffer = lineBuffer.slice(idx + 1);
776
+ handleLine(line);
712
777
  }
713
- else {
714
- message = event.text;
715
- scheduleCompletionGrace();
778
+ });
779
+ child.stderr?.setEncoding("utf8");
780
+ child.stderr?.on("data", (chunk) => {
781
+ stderr += chunk.toString();
782
+ });
783
+ child.stdin?.on("error", (error) => {
784
+ if (settled || isIgnorableStdinError(error))
785
+ return;
786
+ finishReject(new Error(`AUTH_ERROR: codex CLI stdin failed (${error.message})`));
787
+ });
788
+ child.on("error", (error) => {
789
+ finishReject(new Error(`AUTH_ERROR: codex CLI failed (${error.message})`));
790
+ });
791
+ child.on("close", (code) => {
792
+ if (lineBuffer.trim()) {
793
+ handleLine(lineBuffer);
794
+ lineBuffer = "";
716
795
  }
717
- hooks?.onAssistantEvent?.(event, normalized);
718
- }
719
- const parsedError = extractProtocolError(parsed);
720
- if (parsedError) {
721
- protocolError = parsedError;
722
- }
723
- if (isCompletionEvent(parsed)) {
724
- scheduleCompletionGrace();
725
- }
726
- };
727
- const timeoutHandle = setTimeout(() => {
728
- if (settled)
729
- return;
730
- forcedExit = true;
731
- safeKill(child, "SIGTERM");
732
- scheduleHardKill(child);
733
- const detail = stderr.trim().length > 0 ? `: ${stderr.trim()}` : "";
734
- finishReject(new Error(`AUTH_ERROR: codex CLI timed out after ${timeoutMs}ms${detail}`));
735
- }, timeoutMs);
736
- timeoutHandle.unref();
737
- child.stdout?.setEncoding("utf8");
738
- child.stdout?.on("data", (chunk) => {
739
- const text = chunk.toString();
740
- raw += text;
741
- lineBuffer += text;
742
- let idx;
743
- while ((idx = lineBuffer.indexOf("\n")) !== -1) {
744
- const line = lineBuffer.slice(0, idx);
745
- lineBuffer = lineBuffer.slice(idx + 1);
746
- handleLine(line);
747
- }
748
- });
749
- child.stderr?.setEncoding("utf8");
750
- child.stderr?.on("data", (chunk) => {
751
- stderr += chunk.toString();
752
- });
753
- child.stdin?.on("error", (error) => {
754
- if (settled || isIgnorableStdinError(error))
755
- return;
756
- finishReject(new Error(`AUTH_ERROR: codex CLI stdin failed (${error.message})`));
757
- });
758
- child.on("error", (error) => {
759
- finishReject(new Error(`AUTH_ERROR: codex CLI failed (${error.message})`));
760
- });
761
- child.on("close", (code) => {
762
- if (lineBuffer.trim()) {
763
- handleLine(lineBuffer);
764
- lineBuffer = "";
765
- }
766
- if (settled)
767
- return;
768
- if ((code ?? 0) !== 0) {
769
- finishReject(new Error(`AUTH_ERROR: codex CLI failed (exit ${code ?? 0}): ${stderr || protocolError || "no output"}`));
770
- return;
796
+ if (settled)
797
+ return;
798
+ if ((code ?? 0) !== 0) {
799
+ finishReject(new Error(`AUTH_ERROR: codex CLI failed (exit ${code ?? 0}): ${stderr || protocolError || "no output"}`));
800
+ return;
801
+ }
802
+ if (protocolError && !finalizeOutput()) {
803
+ finishReject(new Error(`AUTH_ERROR: codex CLI failed (${protocolError})`));
804
+ return;
805
+ }
806
+ finishResolve();
807
+ });
808
+ try {
809
+ child.stdin?.write(prompt);
810
+ child.stdin?.end();
771
811
  }
772
- if (protocolError && !finalizeOutput()) {
773
- finishReject(new Error(`AUTH_ERROR: codex CLI failed (${protocolError})`));
774
- return;
812
+ catch (error) {
813
+ finishReject(new Error(`AUTH_ERROR: codex CLI stdin failed (${error.message})`));
775
814
  }
776
- finishResolve();
777
815
  });
778
- try {
779
- child.stdin?.write(prompt);
780
- child.stdin?.end();
781
- }
782
- catch (error) {
783
- finishReject(new Error(`AUTH_ERROR: codex CLI stdin failed (${error.message})`));
784
- }
785
- });
816
+ }
817
+ finally {
818
+ await schemaFile?.cleanup();
819
+ }
786
820
  };
787
821
  export const cliHealthy = (throwOnError = false) => {
788
822
  if (process.env.MCODA_CLI_STUB === "1") {
@@ -816,17 +850,17 @@ export const cliHealthy = (throwOnError = false) => {
816
850
  }
817
851
  return { ok: true, details: { version: result.stdout?.toString().trim() } };
818
852
  };
819
- export const runCodexExec = async (prompt, model) => {
853
+ export const runCodexExec = async (prompt, model, outputSchema, timeoutMs) => {
820
854
  if (process.env.MCODA_CLI_STUB === "1") {
821
855
  const output = `qa-stub:${prompt}`;
822
856
  const raw = JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: output } });
823
857
  return { output, raw };
824
858
  }
825
859
  cliHealthy(true);
826
- const result = await invokeCodexProcess(prompt, model);
860
+ const result = await invokeCodexProcess(prompt, model, { outputSchema, timeoutMs });
827
861
  return { output: result.output, raw: result.raw };
828
862
  };
829
- export async function* runCodexExecStream(prompt, model) {
863
+ export async function* runCodexExecStream(prompt, model, outputSchema, timeoutMs) {
830
864
  if (process.env.MCODA_CLI_STUB === "1") {
831
865
  const output = `qa-stub:${prompt}\n`;
832
866
  const raw = JSON.stringify({ type: "item.delta", item: { type: "agent_message", text: output } });
@@ -841,28 +875,47 @@ export async function* runCodexExecStream(prompt, model) {
841
875
  let done = false;
842
876
  let failure;
843
877
  let sawDelta = false;
878
+ let assistantDeltaBuffer = "";
844
879
  const notify = () => {
845
880
  while (waiters.length) {
846
881
  waiters.shift()?.();
847
882
  }
848
883
  };
849
884
  void invokeCodexProcess(prompt, model, {
850
- onLine: (line) => {
851
- formatter.handleLine(line);
852
- },
853
- onAssistantEvent: (event, rawLine) => {
854
- if (event.kind === "delta") {
855
- sawDelta = true;
856
- queue.push({ output: event.text, raw: rawLine });
857
- notify();
858
- return;
859
- }
860
- if (!sawDelta) {
861
- const output = event.text.endsWith("\n") ? event.text : `${event.text}\n`;
885
+ outputSchema,
886
+ timeoutMs,
887
+ hooks: {
888
+ onLine: (line) => {
889
+ formatter.handleLine(line);
890
+ },
891
+ onAssistantEvent: (event, rawLine) => {
892
+ if (event.kind === "delta") {
893
+ sawDelta = true;
894
+ assistantDeltaBuffer += event.text;
895
+ queue.push({ output: event.text, raw: rawLine });
896
+ notify();
897
+ return;
898
+ }
899
+ const finalText = event.text;
900
+ const bufferedText = assistantDeltaBuffer;
901
+ assistantDeltaBuffer = "";
902
+ if (!sawDelta) {
903
+ const output = finalText.endsWith("\n") ? finalText : `${finalText}\n`;
904
+ queue.push({ output, raw: rawLine });
905
+ notify();
906
+ return;
907
+ }
908
+ sawDelta = false;
909
+ const normalizedFinal = normalizeComparableAssistantText(finalText);
910
+ const normalizedBuffered = normalizeComparableAssistantText(bufferedText);
911
+ if (!normalizedFinal || normalizedFinal === normalizedBuffered) {
912
+ return;
913
+ }
914
+ const suffix = finalText.startsWith(bufferedText) ? finalText.slice(bufferedText.length) : finalText;
915
+ const output = suffix.endsWith("\n") ? suffix : `${suffix}\n`;
862
916
  queue.push({ output, raw: rawLine });
863
917
  notify();
864
- }
865
- sawDelta = false;
918
+ },
866
919
  },
867
920
  })
868
921
  .catch((error) => {
@@ -1,11 +1,30 @@
1
1
  import { AgentHealth } from "@mcoda/shared";
2
2
  import { AdapterConfig, AgentAdapter, InvocationRequest, InvocationResult } from "../AdapterTypes.js";
3
+ type OpenAiConfig = AdapterConfig & {
4
+ baseUrl?: string;
5
+ endpoint?: string;
6
+ apiBaseUrl?: string;
7
+ headers?: Record<string, string>;
8
+ temperature?: number;
9
+ extraBody?: Record<string, unknown>;
10
+ };
3
11
  export declare class OpenAiAdapter implements AgentAdapter {
4
12
  private config;
5
- constructor(config: AdapterConfig);
13
+ private baseUrl;
14
+ private headers;
15
+ private temperature;
16
+ private extraBody;
17
+ constructor(config: OpenAiConfig);
6
18
  getCapabilities(): Promise<string[]>;
7
19
  healthCheck(): Promise<AgentHealth>;
8
20
  invoke(request: InvocationRequest): Promise<InvocationResult>;
9
21
  invokeStream(request: InvocationRequest): AsyncGenerator<InvocationResult, void, unknown>;
22
+ private assertConfig;
23
+ private ensureBaseUrl;
24
+ private ensureModel;
25
+ private ensureApiKey;
26
+ private buildHeaders;
27
+ private buildBody;
10
28
  }
29
+ export {};
11
30
  //# sourceMappingURL=OpenAiAdapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"OpenAiAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/openai/OpenAiAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtG,qBAAa,aAAc,YAAW,YAAY;IACpC,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEnC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAkBnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgB5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;CAWjG"}
1
+ {"version":3,"file":"OpenAiAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/openai/OpenAiAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAwHtG,KAAK,YAAY,GAAG,aAAa,GAAG;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC,CAAC;AAEF,qBAAa,aAAc,YAAW,YAAY;IAMpC,OAAO,CAAC,MAAM;IAL1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,SAAS,CAAsC;gBAEnC,MAAM,EAAE,YAAY;IAUlC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAkBnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwC5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;IAwFhG,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,SAAS;CAmBlB"}
@@ -1,6 +1,123 @@
1
+ const DEFAULT_BASE_URL = "https://api.openai.com/v1";
2
+ const asString = (value) => (typeof value === "string" ? value : undefined);
3
+ const resolveString = (value) => {
4
+ const raw = asString(value)?.trim();
5
+ return raw ? raw : undefined;
6
+ };
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const normalizeBaseUrl = (value) => {
9
+ const str = resolveString(value);
10
+ if (!str)
11
+ return undefined;
12
+ return str.endsWith("/") ? str.slice(0, -1) : str;
13
+ };
14
+ const resolveBaseUrl = (config) => {
15
+ const anyConfig = config;
16
+ const agentConfig = config.agent?.config;
17
+ return (normalizeBaseUrl(anyConfig.baseUrl) ??
18
+ normalizeBaseUrl(anyConfig.endpoint) ??
19
+ normalizeBaseUrl(anyConfig.apiBaseUrl) ??
20
+ normalizeBaseUrl(agentConfig?.baseUrl) ??
21
+ normalizeBaseUrl(agentConfig?.endpoint) ??
22
+ normalizeBaseUrl(agentConfig?.apiBaseUrl) ??
23
+ DEFAULT_BASE_URL);
24
+ };
25
+ const extractUsage = (usage) => {
26
+ if (!isRecord(usage))
27
+ return undefined;
28
+ const tokensPrompt = typeof usage.prompt_tokens === "number"
29
+ ? usage.prompt_tokens
30
+ : typeof usage.promptTokens === "number"
31
+ ? usage.promptTokens
32
+ : undefined;
33
+ const tokensCompletion = typeof usage.completion_tokens === "number"
34
+ ? usage.completion_tokens
35
+ : typeof usage.completionTokens === "number"
36
+ ? usage.completionTokens
37
+ : undefined;
38
+ let tokensTotal = typeof usage.total_tokens === "number"
39
+ ? usage.total_tokens
40
+ : typeof usage.totalTokens === "number"
41
+ ? usage.totalTokens
42
+ : undefined;
43
+ if (tokensTotal === undefined && typeof tokensPrompt === "number" && typeof tokensCompletion === "number") {
44
+ tokensTotal = tokensPrompt + tokensCompletion;
45
+ }
46
+ if (tokensPrompt === undefined && tokensCompletion === undefined && tokensTotal === undefined) {
47
+ return undefined;
48
+ }
49
+ return { tokensPrompt, tokensCompletion, tokensTotal };
50
+ };
51
+ const collectContentText = (value) => {
52
+ const direct = asString(value);
53
+ if (direct !== undefined)
54
+ return [direct];
55
+ if (Array.isArray(value))
56
+ return value.flatMap((entry) => collectContentText(entry));
57
+ if (!isRecord(value))
58
+ return [];
59
+ const partType = resolveString(value.type)?.toLowerCase();
60
+ if (partType?.startsWith("reasoning"))
61
+ return [];
62
+ if (asString(value.text) !== undefined)
63
+ return [value.text];
64
+ if (asString(value.output_text) !== undefined)
65
+ return [value.output_text];
66
+ if (asString(value.input_text) !== undefined)
67
+ return [value.input_text];
68
+ if ("content" in value)
69
+ return collectContentText(value.content);
70
+ return [];
71
+ };
72
+ const collectReasoningText = (value) => {
73
+ const direct = asString(value);
74
+ if (direct !== undefined)
75
+ return [direct];
76
+ if (Array.isArray(value))
77
+ return value.flatMap((entry) => collectReasoningText(entry));
78
+ if (!isRecord(value))
79
+ return [];
80
+ const partType = resolveString(value.type)?.toLowerCase();
81
+ if (partType?.startsWith("reasoning")) {
82
+ if (asString(value.text) !== undefined)
83
+ return [value.text];
84
+ if ("content" in value)
85
+ return collectReasoningText(value.content);
86
+ }
87
+ const reasoningFields = [value.reasoning_content, value.reasoning_text, value.summary, value.reasoning];
88
+ const segments = reasoningFields.flatMap((entry) => collectReasoningText(entry));
89
+ if (segments.length > 0)
90
+ return segments;
91
+ return [];
92
+ };
93
+ const collapseText = (segments) => {
94
+ const joined = segments.join("");
95
+ const trimmed = joined.trim();
96
+ return trimmed ? trimmed : undefined;
97
+ };
98
+ const extractResponseText = (data) => {
99
+ const payload = isRecord(data) ? data : {};
100
+ const choices = Array.isArray(payload.choices) ? payload.choices : [];
101
+ const choice = choices[0];
102
+ const message = isRecord(choice) && (isRecord(choice.message) ? choice.message : isRecord(choice.delta) ? choice.delta : {})
103
+ ? (isRecord(choice.message) ? choice.message : choice.delta)
104
+ : {};
105
+ const content = collapseText(collectContentText(message.content ?? message));
106
+ const reasoning = collapseText(collectReasoningText(message.reasoning ?? message));
107
+ const fallback = resolveString(payload.output_text) ??
108
+ collapseText(collectContentText(payload.output ?? payload.response ?? payload.data));
109
+ return { output: content ?? reasoning ?? fallback, reasoning };
110
+ };
1
111
  export class OpenAiAdapter {
2
112
  constructor(config) {
3
113
  this.config = config;
114
+ this.baseUrl = resolveBaseUrl(config);
115
+ this.headers = isRecord(config.headers) ? config.headers : undefined;
116
+ this.temperature = typeof config.temperature === "number" ? config.temperature : undefined;
117
+ this.extraBody = isRecord(config.extraBody)
118
+ ? config.extraBody
119
+ : undefined;
120
+ this.assertConfig();
4
121
  }
5
122
  async getCapabilities() {
6
123
  return this.config.capabilities;
@@ -19,33 +136,189 @@ export class OpenAiAdapter {
19
136
  status: "healthy",
20
137
  lastCheckedAt: new Date().toISOString(),
21
138
  latencyMs: 0,
22
- details: { adapter: "openai-api", model: this.config.model },
139
+ details: { adapter: "openai-api", model: this.config.model, baseUrl: this.baseUrl },
23
140
  };
24
141
  }
25
142
  async invoke(request) {
26
- const authMode = this.config.apiKey ? "api" : "none";
143
+ const url = this.ensureBaseUrl();
144
+ const model = this.ensureModel();
145
+ const apiKey = this.ensureApiKey();
146
+ const resp = await fetch(`${url}/chat/completions`, {
147
+ method: "POST",
148
+ headers: this.buildHeaders(apiKey, false),
149
+ body: JSON.stringify(this.buildBody(request.input, model, false)),
150
+ });
151
+ if (!resp.ok) {
152
+ const text = await resp.text().catch(() => "");
153
+ throw new Error(`OpenAI chat completions failed (${resp.status}): ${text}`);
154
+ }
155
+ const data = await resp.json().catch(() => ({}));
156
+ const usage = extractUsage(isRecord(data) ? data.usage : undefined);
157
+ const { output, reasoning } = extractResponseText(data);
27
158
  return {
28
- output: `openai-stub:${request.input}`,
159
+ output: (output ?? JSON.stringify(data)).trim(),
29
160
  adapter: this.config.adapter ?? "openai-api",
30
- model: this.config.model,
161
+ model,
31
162
  metadata: {
32
- mode: authMode,
163
+ mode: "api",
33
164
  capabilities: this.config.capabilities,
34
165
  prompts: this.config.prompts,
35
- authMode,
166
+ authMode: "api",
36
167
  adapterType: this.config.adapter ?? "openai-api",
168
+ baseUrl: url,
169
+ usage: isRecord(data) ? data.usage : undefined,
170
+ tokensPrompt: usage?.tokensPrompt,
171
+ tokensCompletion: usage?.tokensCompletion,
172
+ tokensTotal: usage?.tokensTotal,
173
+ tokens_prompt: usage?.tokensPrompt,
174
+ tokens_completion: usage?.tokensCompletion,
175
+ tokens_total: usage?.tokensTotal,
176
+ reasoning,
37
177
  },
38
178
  };
39
179
  }
40
180
  async *invokeStream(request) {
41
- yield {
42
- output: `openai-stream:${request.input}`,
43
- adapter: this.config.adapter ?? "openai-api",
44
- model: this.config.model,
45
- metadata: {
46
- mode: this.config.apiKey ? "api" : "none",
47
- streaming: true,
48
- },
181
+ const url = this.ensureBaseUrl();
182
+ const model = this.ensureModel();
183
+ const apiKey = this.ensureApiKey();
184
+ const resp = await fetch(`${url}/chat/completions`, {
185
+ method: "POST",
186
+ headers: this.buildHeaders(apiKey, true),
187
+ body: JSON.stringify(this.buildBody(request.input, model, true)),
188
+ });
189
+ if (!resp.ok || !resp.body) {
190
+ const text = !resp.ok ? await resp.text().catch(() => "") : "";
191
+ throw new Error(`OpenAI chat completions (stream) failed (${resp.status}): ${text}`);
192
+ }
193
+ const reader = resp.body.getReader();
194
+ const decoder = new TextDecoder();
195
+ let buffer = "";
196
+ let latestUsage;
197
+ const buildChunk = (payload) => {
198
+ const data = JSON.parse(payload);
199
+ const usage = extractUsage(isRecord(data) ? data.usage : undefined);
200
+ if (usage)
201
+ latestUsage = usage;
202
+ const { output, reasoning } = extractResponseText(data);
203
+ if (!output && !usage)
204
+ return null;
205
+ return {
206
+ output: output ?? "",
207
+ adapter: this.config.adapter ?? "openai-api",
208
+ model,
209
+ metadata: {
210
+ mode: "api",
211
+ authMode: "api",
212
+ adapterType: this.config.adapter ?? "openai-api",
213
+ baseUrl: url,
214
+ capabilities: this.config.capabilities,
215
+ prompts: this.config.prompts,
216
+ streaming: true,
217
+ usage: isRecord(data) ? data.usage : undefined,
218
+ tokensPrompt: latestUsage?.tokensPrompt,
219
+ tokensCompletion: latestUsage?.tokensCompletion,
220
+ tokensTotal: latestUsage?.tokensTotal,
221
+ tokens_prompt: latestUsage?.tokensPrompt,
222
+ tokens_completion: latestUsage?.tokensCompletion,
223
+ tokens_total: latestUsage?.tokensTotal,
224
+ reasoning,
225
+ raw: payload,
226
+ },
227
+ };
228
+ };
229
+ while (true) {
230
+ const { value, done } = await reader.read();
231
+ if (done)
232
+ break;
233
+ buffer += decoder.decode(value, { stream: true });
234
+ let idx;
235
+ while ((idx = buffer.indexOf("\n")) !== -1) {
236
+ const line = buffer.slice(0, idx).trim();
237
+ buffer = buffer.slice(idx + 1);
238
+ if (!line || !line.startsWith("data:"))
239
+ continue;
240
+ const payload = line.slice(5).trim();
241
+ if (!payload)
242
+ continue;
243
+ if (payload === "[DONE]")
244
+ return;
245
+ try {
246
+ const chunk = buildChunk(payload);
247
+ if (chunk)
248
+ yield chunk;
249
+ }
250
+ catch {
251
+ // Ignore malformed SSE lines and continue streaming.
252
+ }
253
+ }
254
+ }
255
+ const tail = buffer.trim();
256
+ if (!tail)
257
+ return;
258
+ const lines = tail.split(/\r?\n/).filter((line) => line.trim().length > 0);
259
+ for (const line of lines) {
260
+ const trimmed = line.trim();
261
+ if (!trimmed.startsWith("data:"))
262
+ continue;
263
+ const payload = trimmed.slice(5).trim();
264
+ if (!payload || payload === "[DONE]")
265
+ continue;
266
+ try {
267
+ const chunk = buildChunk(payload);
268
+ if (chunk)
269
+ yield chunk;
270
+ }
271
+ catch {
272
+ // Ignore malformed SSE lines and continue streaming.
273
+ }
274
+ }
275
+ }
276
+ assertConfig() {
277
+ if (!/^https?:\/\//i.test(this.baseUrl)) {
278
+ throw new Error("OpenAI baseUrl must start with http:// or https://");
279
+ }
280
+ }
281
+ ensureBaseUrl() {
282
+ return this.baseUrl;
283
+ }
284
+ ensureModel() {
285
+ if (!this.config.model) {
286
+ throw new Error("OpenAI model is not configured for this agent");
287
+ }
288
+ return this.config.model;
289
+ }
290
+ ensureApiKey() {
291
+ if (!this.config.apiKey) {
292
+ throw new Error(`AUTH_REQUIRED: OpenAI API key missing; run \`mcoda agent auth set ${this.config.agent.slug ?? this.config.agent.id}\``);
293
+ }
294
+ return this.config.apiKey;
295
+ }
296
+ buildHeaders(apiKey, streaming) {
297
+ return {
298
+ Authorization: `Bearer ${apiKey}`,
299
+ "Content-Type": "application/json",
300
+ Accept: streaming ? "text/event-stream" : "application/json",
301
+ ...(this.headers ?? {}),
302
+ };
303
+ }
304
+ buildBody(input, model, stream) {
305
+ const body = {
306
+ model,
307
+ messages: [{ role: "user", content: input }],
308
+ stream,
49
309
  };
310
+ if (typeof this.temperature === "number") {
311
+ body.temperature = this.temperature;
312
+ }
313
+ if (this.extraBody) {
314
+ for (const [key, value] of Object.entries(this.extraBody)) {
315
+ if (body[key] === undefined)
316
+ body[key] = value;
317
+ }
318
+ }
319
+ if (stream && body.stream_options === undefined) {
320
+ body.stream_options = { include_usage: true };
321
+ }
322
+ return body;
50
323
  }
51
324
  }
@@ -4,7 +4,7 @@ type ZhipuConfig = AdapterConfig & {
4
4
  baseUrl?: string;
5
5
  headers?: Record<string, string>;
6
6
  temperature?: number;
7
- thinking?: boolean;
7
+ thinking?: boolean | Record<string, unknown>;
8
8
  extraBody?: Record<string, unknown>;
9
9
  };
10
10
  export declare class ZhipuApiAdapter implements AgentAdapter {
@@ -1 +1 @@
1
- {"version":3,"file":"ZhipuApiAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/zhipu/ZhipuApiAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AA0CtG,KAAK,WAAW,GAAG,aAAa,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC,CAAC;AAEF,qBAAa,eAAgB,YAAW,YAAY;IAOtC,OAAO,CAAC,MAAM;IAN1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,SAAS,CAAsC;gBAEnC,MAAM,EAAE,WAAW;IASjC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAkBnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2C5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;IA2FhG,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,SAAS;CAgBlB"}
1
+ {"version":3,"file":"ZhipuApiAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/zhipu/ZhipuApiAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AA0DtG,KAAK,WAAW,GAAG,aAAa,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC,CAAC;AAEF,qBAAa,eAAgB,YAAW,YAAY;IAOtC,OAAO,CAAC,MAAM;IAN1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,SAAS,CAAsC;gBAEnC,MAAM,EAAE,WAAW;IASjC,eAAe,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAIpC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAkBnC,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2C5D,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC;IA2FhG,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,SAAS;CAgBlB"}
@@ -1,4 +1,5 @@
1
1
  const DEFAULT_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
2
+ const DEFAULT_CODING_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
2
3
  const DEFAULT_TEMPERATURE = 0.1;
3
4
  const normalizeBaseUrl = (value) => {
4
5
  if (!value)
@@ -9,6 +10,20 @@ const normalizeBaseUrl = (value) => {
9
10
  return str.endsWith("/") ? str.slice(0, -1) : str;
10
11
  };
11
12
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13
+ const usesCodingBaseUrl = (model) => {
14
+ if (!model)
15
+ return false;
16
+ const normalized = model.trim().toLowerCase();
17
+ return normalized === "glm-4.7" || normalized.startsWith("glm-4.7-");
18
+ };
19
+ const defaultBaseUrlForModel = (model) => usesCodingBaseUrl(model) ? DEFAULT_CODING_BASE_URL : DEFAULT_BASE_URL;
20
+ const normalizeThinking = (value) => {
21
+ if (value === true)
22
+ return { type: "enabled" };
23
+ if (value === false)
24
+ return { type: "disabled" };
25
+ return isRecord(value) && Object.keys(value).length > 0 ? value : undefined;
26
+ };
12
27
  const extractUsage = (usage) => {
13
28
  if (!usage || typeof usage !== "object")
14
29
  return undefined;
@@ -37,10 +52,10 @@ const extractUsage = (usage) => {
37
52
  export class ZhipuApiAdapter {
38
53
  constructor(config) {
39
54
  this.config = config;
40
- this.baseUrl = normalizeBaseUrl(config.baseUrl) ?? DEFAULT_BASE_URL;
55
+ this.baseUrl = normalizeBaseUrl(config.baseUrl) ?? defaultBaseUrlForModel(config.model);
41
56
  this.headers = isRecord(config.headers) ? config.headers : undefined;
42
57
  this.temperature = typeof config.temperature === "number" ? config.temperature : undefined;
43
- this.thinking = typeof config.thinking === "boolean" ? config.thinking : undefined;
58
+ this.thinking = normalizeThinking(config.thinking);
44
59
  this.extraBody = isRecord(config.extraBody) ? config.extraBody : undefined;
45
60
  this.assertConfig();
46
61
  }
@@ -242,7 +257,7 @@ export class ZhipuApiAdapter {
242
257
  const temperature = this.temperature ?? DEFAULT_TEMPERATURE;
243
258
  if (typeof temperature === "number")
244
259
  body.temperature = temperature;
245
- if (typeof this.thinking === "boolean")
260
+ if (this.thinking)
246
261
  body.thinking = this.thinking;
247
262
  if (this.extraBody) {
248
263
  for (const [key, value] of Object.entries(this.extraBody)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcoda/agents",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "Agent registry and capabilities for mcoda.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,8 +30,8 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "@mcoda/shared": "0.1.36",
34
- "@mcoda/db": "0.1.36"
33
+ "@mcoda/shared": "0.1.38",
34
+ "@mcoda/db": "0.1.38"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsc -p tsconfig.json",