@mcoda/agents 0.1.37 → 0.1.40

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":"AgentService.d.ts","sourceRoot":"","sources":["../../src/AgentService/AgentService.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,EACL,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAY7C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AA2LhG,UAAU,mBAAmB;IAC3B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,OAAO;gBADP,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,mBAAwB;WAG9B,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAKtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAUhD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAIrE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAInD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAIpD,kBAAkB;YAMlB,kBAAkB;IA6BhC,OAAO,CAAC,kBAAkB;IA+BpB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA4C/E,OAAO,CAAC,KAAK;YAIC,OAAO;YASP,UAAU;IAQxB,OAAO,CAAC,6BAA6B;YAOvB,mBAAmB;YAgCnB,uBAAuB;YAiBvB,gBAAgB;IAM9B,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,8BAA8B;IAStC,OAAO,CAAC,iBAAiB;YAOX,oBAAoB;YA6BpB,oBAAoB;YA6CpB,sBAAsB;YAkBtB,mBAAmB;YAUnB,4BAA4B;IAkCpC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAmBlD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6H9E,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;YAwP5F,mBAAmB;YAkBnB,uBAAuB;YAwBvB,mBAAmB;CAYlC"}
1
+ {"version":3,"file":"AgentService.d.ts","sourceRoot":"","sources":["../../src/AgentService/AgentService.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,EACL,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAY7C,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAyMhG,UAAU,mBAAmB;IAC3B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,OAAO;gBADP,IAAI,EAAE,gBAAgB,EACtB,OAAO,GAAE,mBAAwB;WAG9B,MAAM,IAAI,OAAO,CAAC,YAAY,CAAC;IAKtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAUhD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAIrE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAInD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAIpD,kBAAkB;YAMlB,kBAAkB;IA6BhC,OAAO,CAAC,kBAAkB;IAqCpB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA4C/E,OAAO,CAAC,KAAK;YAIC,OAAO;YASP,UAAU;IAQxB,OAAO,CAAC,6BAA6B;YAOvB,mBAAmB;YAgCnB,uBAAuB;YAiBvB,gBAAgB;IAM9B,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,8BAA8B;IAStC,OAAO,CAAC,iBAAiB;YAOX,oBAAoB;YA6BpB,oBAAoB;YA6CpB,sBAAsB;YAkBtB,mBAAmB;YAUnB,4BAA4B;IAkCpC,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAmBlD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6H9E,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;YAwP5F,mBAAmB;YAkBnB,uBAAuB;YAwBvB,mBAAmB;CAYlC"}
@@ -56,6 +56,17 @@ const WINDOW_RESET_FALLBACK_MS = {
56
56
  weekly: 7 * 24 * 60 * 60 * 1000,
57
57
  other: 60 * 60 * 1000,
58
58
  };
59
+ const isManagedMswarmCloudAgent = (agent) => {
60
+ const config = agent.config;
61
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
62
+ return false;
63
+ }
64
+ const managed = config.mswarmCloud;
65
+ return Boolean(managed &&
66
+ typeof managed === "object" &&
67
+ !Array.isArray(managed) &&
68
+ managed.managed === true);
69
+ };
59
70
  const isIoEnabled = () => {
60
71
  const raw = process.env[IO_ENV];
61
72
  if (!raw)
@@ -266,6 +277,10 @@ export class AgentService {
266
277
  if (adapterType.endsWith("-api")) {
267
278
  if (hasSecret)
268
279
  return adapterType;
280
+ if (adapterType === "openai-api" && isManagedMswarmCloudAgent(agent)) {
281
+ const label = agent.slug ?? agent.id;
282
+ throw new Error(`AUTH_REQUIRED: Managed mswarm cloud agent ${label} is missing the synced API key; run \`mcoda config set mswarm-api-key <KEY>\` and \`mcoda cloud agent sync\`.`);
283
+ }
269
284
  if (adapterType === "codex-api" || adapterType === "openai-api") {
270
285
  // Default to the codex CLI when API creds are missing.
271
286
  adapterType = "codex-cli";
@@ -1,11 +1,31 @@
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;
28
+ private buildHealthCheckBody;
10
29
  }
30
+ export {};
11
31
  //# 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;IAgFnC,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;IAoBjB,OAAO,CAAC,oBAAoB;CAU7B"}
@@ -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;
@@ -11,41 +128,270 @@ export class OpenAiAdapter {
11
128
  agentId: this.config.agent.id,
12
129
  status: "unreachable",
13
130
  lastCheckedAt: new Date().toISOString(),
14
- details: { reason: "missing_api_key" },
131
+ details: {
132
+ adapter: "openai-api",
133
+ source: "openai_probe",
134
+ model: this.config.model,
135
+ baseUrl: this.baseUrl,
136
+ reason: "missing_api_key",
137
+ },
138
+ };
139
+ }
140
+ const startedAt = Date.now();
141
+ try {
142
+ const model = this.ensureModel();
143
+ const apiKey = this.ensureApiKey();
144
+ const url = this.ensureBaseUrl();
145
+ const response = await fetch(`${url}/chat/completions`, {
146
+ method: "POST",
147
+ headers: this.buildHeaders(apiKey, false),
148
+ body: JSON.stringify(this.buildHealthCheckBody(model)),
149
+ });
150
+ const responseText = await response.text().catch(() => "");
151
+ const latencyMs = Date.now() - startedAt;
152
+ if (!response.ok) {
153
+ return {
154
+ agentId: this.config.agent.id,
155
+ status: response.status === 429 ? "degraded" : "unreachable",
156
+ lastCheckedAt: new Date().toISOString(),
157
+ latencyMs,
158
+ details: {
159
+ adapter: "openai-api",
160
+ source: "openai_probe",
161
+ model,
162
+ baseUrl: url,
163
+ reason: "http_error",
164
+ httpStatus: response.status,
165
+ response: responseText.slice(0, 500),
166
+ },
167
+ };
168
+ }
169
+ return {
170
+ agentId: this.config.agent.id,
171
+ status: "healthy",
172
+ lastCheckedAt: new Date().toISOString(),
173
+ latencyMs,
174
+ details: {
175
+ adapter: "openai-api",
176
+ source: "openai_probe",
177
+ model,
178
+ baseUrl: url,
179
+ },
180
+ };
181
+ }
182
+ catch (error) {
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ const reason = /model is not configured/i.test(message)
185
+ ? "missing_model"
186
+ : /missing api key/i.test(message)
187
+ ? "missing_api_key"
188
+ : "probe_failed";
189
+ return {
190
+ agentId: this.config.agent.id,
191
+ status: "unreachable",
192
+ lastCheckedAt: new Date().toISOString(),
193
+ latencyMs: Date.now() - startedAt,
194
+ details: {
195
+ adapter: "openai-api",
196
+ source: "openai_probe",
197
+ model: this.config.model,
198
+ baseUrl: this.baseUrl,
199
+ reason,
200
+ error: message,
201
+ },
15
202
  };
16
203
  }
17
- return {
18
- agentId: this.config.agent.id,
19
- status: "healthy",
20
- lastCheckedAt: new Date().toISOString(),
21
- latencyMs: 0,
22
- details: { adapter: "openai-api", model: this.config.model },
23
- };
24
204
  }
25
205
  async invoke(request) {
26
- const authMode = this.config.apiKey ? "api" : "none";
206
+ const url = this.ensureBaseUrl();
207
+ const model = this.ensureModel();
208
+ const apiKey = this.ensureApiKey();
209
+ const resp = await fetch(`${url}/chat/completions`, {
210
+ method: "POST",
211
+ headers: this.buildHeaders(apiKey, false),
212
+ body: JSON.stringify(this.buildBody(request.input, model, false)),
213
+ });
214
+ if (!resp.ok) {
215
+ const text = await resp.text().catch(() => "");
216
+ throw new Error(`OpenAI chat completions failed (${resp.status}): ${text}`);
217
+ }
218
+ const data = await resp.json().catch(() => ({}));
219
+ const usage = extractUsage(isRecord(data) ? data.usage : undefined);
220
+ const { output, reasoning } = extractResponseText(data);
27
221
  return {
28
- output: `openai-stub:${request.input}`,
222
+ output: (output ?? JSON.stringify(data)).trim(),
29
223
  adapter: this.config.adapter ?? "openai-api",
30
- model: this.config.model,
224
+ model,
31
225
  metadata: {
32
- mode: authMode,
226
+ mode: "api",
33
227
  capabilities: this.config.capabilities,
34
228
  prompts: this.config.prompts,
35
- authMode,
229
+ authMode: "api",
36
230
  adapterType: this.config.adapter ?? "openai-api",
231
+ baseUrl: url,
232
+ usage: isRecord(data) ? data.usage : undefined,
233
+ tokensPrompt: usage?.tokensPrompt,
234
+ tokensCompletion: usage?.tokensCompletion,
235
+ tokensTotal: usage?.tokensTotal,
236
+ tokens_prompt: usage?.tokensPrompt,
237
+ tokens_completion: usage?.tokensCompletion,
238
+ tokens_total: usage?.tokensTotal,
239
+ reasoning,
37
240
  },
38
241
  };
39
242
  }
40
243
  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
- },
244
+ const url = this.ensureBaseUrl();
245
+ const model = this.ensureModel();
246
+ const apiKey = this.ensureApiKey();
247
+ const resp = await fetch(`${url}/chat/completions`, {
248
+ method: "POST",
249
+ headers: this.buildHeaders(apiKey, true),
250
+ body: JSON.stringify(this.buildBody(request.input, model, true)),
251
+ });
252
+ if (!resp.ok || !resp.body) {
253
+ const text = !resp.ok ? await resp.text().catch(() => "") : "";
254
+ throw new Error(`OpenAI chat completions (stream) failed (${resp.status}): ${text}`);
255
+ }
256
+ const reader = resp.body.getReader();
257
+ const decoder = new TextDecoder();
258
+ let buffer = "";
259
+ let latestUsage;
260
+ const buildChunk = (payload) => {
261
+ const data = JSON.parse(payload);
262
+ const usage = extractUsage(isRecord(data) ? data.usage : undefined);
263
+ if (usage)
264
+ latestUsage = usage;
265
+ const { output, reasoning } = extractResponseText(data);
266
+ if (!output && !usage)
267
+ return null;
268
+ return {
269
+ output: output ?? "",
270
+ adapter: this.config.adapter ?? "openai-api",
271
+ model,
272
+ metadata: {
273
+ mode: "api",
274
+ authMode: "api",
275
+ adapterType: this.config.adapter ?? "openai-api",
276
+ baseUrl: url,
277
+ capabilities: this.config.capabilities,
278
+ prompts: this.config.prompts,
279
+ streaming: true,
280
+ usage: isRecord(data) ? data.usage : undefined,
281
+ tokensPrompt: latestUsage?.tokensPrompt,
282
+ tokensCompletion: latestUsage?.tokensCompletion,
283
+ tokensTotal: latestUsage?.tokensTotal,
284
+ tokens_prompt: latestUsage?.tokensPrompt,
285
+ tokens_completion: latestUsage?.tokensCompletion,
286
+ tokens_total: latestUsage?.tokensTotal,
287
+ reasoning,
288
+ raw: payload,
289
+ },
290
+ };
291
+ };
292
+ while (true) {
293
+ const { value, done } = await reader.read();
294
+ if (done)
295
+ break;
296
+ buffer += decoder.decode(value, { stream: true });
297
+ let idx;
298
+ while ((idx = buffer.indexOf("\n")) !== -1) {
299
+ const line = buffer.slice(0, idx).trim();
300
+ buffer = buffer.slice(idx + 1);
301
+ if (!line || !line.startsWith("data:"))
302
+ continue;
303
+ const payload = line.slice(5).trim();
304
+ if (!payload)
305
+ continue;
306
+ if (payload === "[DONE]")
307
+ return;
308
+ try {
309
+ const chunk = buildChunk(payload);
310
+ if (chunk)
311
+ yield chunk;
312
+ }
313
+ catch {
314
+ // Ignore malformed SSE lines and continue streaming.
315
+ }
316
+ }
317
+ }
318
+ const tail = buffer.trim();
319
+ if (!tail)
320
+ return;
321
+ const lines = tail.split(/\r?\n/).filter((line) => line.trim().length > 0);
322
+ for (const line of lines) {
323
+ const trimmed = line.trim();
324
+ if (!trimmed.startsWith("data:"))
325
+ continue;
326
+ const payload = trimmed.slice(5).trim();
327
+ if (!payload || payload === "[DONE]")
328
+ continue;
329
+ try {
330
+ const chunk = buildChunk(payload);
331
+ if (chunk)
332
+ yield chunk;
333
+ }
334
+ catch {
335
+ // Ignore malformed SSE lines and continue streaming.
336
+ }
337
+ }
338
+ }
339
+ assertConfig() {
340
+ if (!/^https?:\/\//i.test(this.baseUrl)) {
341
+ throw new Error("OpenAI baseUrl must start with http:// or https://");
342
+ }
343
+ }
344
+ ensureBaseUrl() {
345
+ return this.baseUrl;
346
+ }
347
+ ensureModel() {
348
+ if (!this.config.model) {
349
+ throw new Error("OpenAI model is not configured for this agent");
350
+ }
351
+ return this.config.model;
352
+ }
353
+ ensureApiKey() {
354
+ if (!this.config.apiKey) {
355
+ throw new Error(`AUTH_REQUIRED: OpenAI API key missing; run \`mcoda agent auth set ${this.config.agent.slug ?? this.config.agent.id}\``);
356
+ }
357
+ return this.config.apiKey;
358
+ }
359
+ buildHeaders(apiKey, streaming) {
360
+ return {
361
+ Authorization: `Bearer ${apiKey}`,
362
+ "Content-Type": "application/json",
363
+ Accept: streaming ? "text/event-stream" : "application/json",
364
+ ...(this.headers ?? {}),
49
365
  };
50
366
  }
367
+ buildBody(input, model, stream) {
368
+ const body = {
369
+ model,
370
+ messages: [{ role: "user", content: input }],
371
+ stream,
372
+ };
373
+ if (typeof this.temperature === "number") {
374
+ body.temperature = this.temperature;
375
+ }
376
+ if (this.extraBody) {
377
+ for (const [key, value] of Object.entries(this.extraBody)) {
378
+ if (body[key] === undefined)
379
+ body[key] = value;
380
+ }
381
+ }
382
+ if (stream && body.stream_options === undefined) {
383
+ body.stream_options = { include_usage: true };
384
+ }
385
+ return body;
386
+ }
387
+ buildHealthCheckBody(model) {
388
+ const body = this.buildBody("healthcheck", model, false);
389
+ if (body.max_tokens === undefined && body.max_completion_tokens === undefined) {
390
+ body.max_tokens = 1;
391
+ }
392
+ if (body.temperature === undefined) {
393
+ body.temperature = 0;
394
+ }
395
+ return body;
396
+ }
51
397
  }
@@ -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.37",
3
+ "version": "0.1.40",
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.37",
34
- "@mcoda/db": "0.1.37"
33
+ "@mcoda/shared": "0.1.40",
34
+ "@mcoda/db": "0.1.40"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsc -p tsconfig.json",