@morphllm/subagents 0.1.3 → 0.1.4

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.
@@ -67,7 +67,7 @@ var BaseClient = class {
67
67
  };
68
68
 
69
69
  // src/docx/client.ts
70
- var DEFAULT_API_URL = "https://docx-api-service-production.up.railway.app";
70
+ var DEFAULT_API_URL = "https://subagents.morphllm.com/v1/morph-docx";
71
71
  var DocxClient = class extends BaseClient {
72
72
  constructor(options = {}) {
73
73
  super(options);
@@ -113,333 +113,60 @@ var DocxClient = class extends BaseClient {
113
113
  async deleteDocument(docId) {
114
114
  return this.delete(`/documents/${docId}`);
115
115
  }
116
- };
117
-
118
- // src/core/base-agent.ts
119
- var BaseAgent = class {
120
- constructor(options) {
121
- this.openai = options.openai;
122
- this.model = options.model || this.getDefaultModel();
123
- this.instructions = options.instructions || this.getDefaultInstructions();
124
- this.client = options.client;
116
+ // --- Creation Operations ---
117
+ /**
118
+ * Create a new empty DOCX document.
119
+ */
120
+ async create(title) {
121
+ return this.post("/documents/create", { title });
125
122
  }
126
- /** Get the underlying client */
127
- getClient() {
128
- return this.client;
123
+ /**
124
+ * Execute build operations on a document (add content).
125
+ */
126
+ async build(docId, operations) {
127
+ return this.post(`/documents/${docId}/build`, { operations });
129
128
  }
130
129
  /**
131
- * Run the agent with a user message.
132
- * Returns the final response and all tool calls made.
130
+ * Get document information.
133
131
  */
134
- async run(userMessage, conversationHistory = []) {
135
- if (!this.openai) {
136
- throw new Error("OpenAI client is required for agent.run(). Pass it in the constructor options.");
137
- }
138
- const messages = [
139
- { role: "system", content: this.instructions },
140
- ...conversationHistory,
141
- { role: "user", content: userMessage }
142
- ];
143
- const toolCalls = [];
144
- let response = "";
145
- const tools = this.getTools();
146
- while (true) {
147
- const completion = await this.openai.chat.completions.create({
148
- model: this.model,
149
- messages,
150
- tools,
151
- tool_choice: "auto"
152
- });
153
- const assistantMessage = completion.choices[0].message;
154
- if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
155
- messages.push({
156
- role: "assistant",
157
- content: assistantMessage.content || ""
158
- });
159
- for (const tc of assistantMessage.tool_calls) {
160
- const fn = tc.function;
161
- const args = JSON.parse(fn.arguments);
162
- const toolCall = {
163
- id: tc.id,
164
- name: fn.name,
165
- arguments: args,
166
- status: "running"
167
- };
168
- toolCalls.push(toolCall);
169
- const result = await this.executeTool(fn.name, args);
170
- if (result.startsWith("Error:")) {
171
- toolCall.error = result;
172
- toolCall.status = "error";
173
- } else {
174
- toolCall.result = result;
175
- toolCall.status = "completed";
176
- }
177
- messages.push({
178
- role: "tool",
179
- content: result,
180
- tool_call_id: tc.id
181
- });
182
- }
183
- } else {
184
- response = assistantMessage.content || "";
185
- break;
186
- }
187
- }
188
- return { response, toolCalls };
132
+ async getInfo(docId) {
133
+ return this.get(`/documents/${docId}/info`);
189
134
  }
135
+ // --- Convenience Methods ---
190
136
  /**
191
- * Run the agent with streaming output.
192
- * Yields events for real-time UI updates.
137
+ * Add a heading to the document.
193
138
  */
194
- async *runStream(userMessage, conversationHistory = []) {
195
- if (!this.openai) {
196
- throw new Error("OpenAI client is required for agent.runStream(). Pass it in the constructor options.");
197
- }
198
- const messages = [
199
- { role: "system", content: this.instructions },
200
- ...conversationHistory,
201
- { role: "user", content: userMessage }
202
- ];
203
- const tools = this.getTools();
204
- while (true) {
205
- yield { type: "message_start", message: { role: "assistant" } };
206
- const stream = await this.openai.chat.completions.create({
207
- model: this.model,
208
- messages,
209
- tools,
210
- tool_choice: "auto",
211
- stream: true
212
- });
213
- let contentBuffer = "";
214
- const toolCallBuffers = /* @__PURE__ */ new Map();
215
- for await (const chunk of stream) {
216
- const delta = chunk.choices[0]?.delta;
217
- if (delta?.content) {
218
- contentBuffer += delta.content;
219
- yield { type: "content_delta", delta: { text: delta.content } };
220
- }
221
- if (delta?.tool_calls) {
222
- for (const tc of delta.tool_calls) {
223
- const idx = tc.index;
224
- if (!toolCallBuffers.has(idx)) {
225
- toolCallBuffers.set(idx, { id: "", name: "", arguments: "" });
226
- }
227
- const buffer = toolCallBuffers.get(idx);
228
- if (tc.id) buffer.id = tc.id;
229
- if (tc.function?.name) buffer.name = tc.function.name;
230
- if (tc.function?.arguments) buffer.arguments += tc.function.arguments;
231
- }
232
- }
233
- }
234
- if (toolCallBuffers.size > 0) {
235
- messages.push({
236
- role: "assistant",
237
- content: contentBuffer
238
- });
239
- for (const [_, buffer] of toolCallBuffers) {
240
- const args = JSON.parse(buffer.arguments);
241
- yield {
242
- type: "tool_start",
243
- tool: { id: buffer.id, name: buffer.name, arguments: args }
244
- };
245
- const result = await this.executeTool(buffer.name, args);
246
- if (result.startsWith("Error:")) {
247
- yield { type: "tool_end", tool: { id: buffer.id, error: result } };
248
- } else {
249
- yield { type: "tool_end", tool: { id: buffer.id, result } };
250
- }
251
- messages.push({
252
- role: "tool",
253
- content: result,
254
- tool_call_id: buffer.id
255
- });
256
- }
257
- } else {
258
- yield { type: "message_end" };
259
- break;
260
- }
261
- }
139
+ async addHeading(docId, text, level = 1) {
140
+ return this.build(docId, [{ type: "add_heading", text, level }]);
141
+ }
142
+ /**
143
+ * Add a paragraph to the document.
144
+ */
145
+ async addParagraph(docId, text, options) {
146
+ return this.build(docId, [{ type: "add_paragraph", text, ...options }]);
147
+ }
148
+ /**
149
+ * Add a table to the document.
150
+ */
151
+ async addTable(docId, headers, rows) {
152
+ return this.build(docId, [{ type: "add_table", headers, rows }]);
153
+ }
154
+ /**
155
+ * Add page numbers to the document.
156
+ */
157
+ async addPageNumbers(docId, format = "Page {PAGE} of {NUMPAGES}") {
158
+ return this.build(docId, [{ type: "add_page_numbers", format_string: format }]);
262
159
  }
263
160
  };
264
161
 
265
162
  // src/docx/agent.ts
266
- var DEFAULT_INSTRUCTIONS = `You are an expert DOCX document editing assistant.
267
-
268
- When editing documents:
269
- 1. First read the document to understand its structure
270
- 2. Use track changes for all modifications
271
- 3. Add comments to flag issues or request clarification
272
- 4. Be precise with paragraph text matching - use unique text snippets
273
-
274
- Available tools:
275
- - read_document: Read the document content
276
- - add_comment: Add a comment to a specific paragraph
277
- - insert_text: Insert text within a paragraph (tracked change)
278
- - propose_deletion: Mark text for deletion (tracked change)
279
- - reply_comment: Reply to an existing comment
280
- - resolve_comment: Mark a comment as resolved
281
- - delete_comment: Delete a comment
282
- - insert_paragraph: Insert a new paragraph
283
- - validate_document: Validate DOCX structure`;
284
- var TOOLS = [
285
- {
286
- type: "function",
287
- function: {
288
- name: "read_document",
289
- description: "Read the document content as plain text",
290
- parameters: {
291
- type: "object",
292
- properties: {
293
- doc_id: { type: "string", description: "Document ID" }
294
- },
295
- required: ["doc_id"]
296
- }
297
- }
298
- },
299
- {
300
- type: "function",
301
- function: {
302
- name: "add_comment",
303
- description: "Add a comment to a specific paragraph in the document",
304
- parameters: {
305
- type: "object",
306
- properties: {
307
- doc_id: { type: "string", description: "Document ID" },
308
- para_text: { type: "string", description: "Unique text from the target paragraph" },
309
- comment: { type: "string", description: "Comment text to add" },
310
- highlight: { type: "string", description: "Optional specific text to highlight" }
311
- },
312
- required: ["doc_id", "para_text", "comment"]
313
- }
314
- }
315
- },
316
- {
317
- type: "function",
318
- function: {
319
- name: "insert_text",
320
- description: "Insert text within a paragraph (creates a tracked change)",
321
- parameters: {
322
- type: "object",
323
- properties: {
324
- doc_id: { type: "string", description: "Document ID" },
325
- para_text: { type: "string", description: "Unique text from the target paragraph" },
326
- after: { type: "string", description: "Text after which to insert" },
327
- new_text: { type: "string", description: "Text to insert" }
328
- },
329
- required: ["doc_id", "para_text", "after", "new_text"]
330
- }
331
- }
332
- },
333
- {
334
- type: "function",
335
- function: {
336
- name: "propose_deletion",
337
- description: "Mark text for deletion (creates a tracked change)",
338
- parameters: {
339
- type: "object",
340
- properties: {
341
- doc_id: { type: "string", description: "Document ID" },
342
- para_text: { type: "string", description: "Unique text from the target paragraph" },
343
- target: { type: "string", description: "Specific text to delete (optional, defaults to whole paragraph)" }
344
- },
345
- required: ["doc_id", "para_text"]
346
- }
347
- }
348
- },
349
- {
350
- type: "function",
351
- function: {
352
- name: "reply_comment",
353
- description: "Reply to an existing comment",
354
- parameters: {
355
- type: "object",
356
- properties: {
357
- doc_id: { type: "string", description: "Document ID" },
358
- comment_id: { type: "string", description: "ID of the comment to reply to" },
359
- reply: { type: "string", description: "Reply text" }
360
- },
361
- required: ["doc_id", "comment_id", "reply"]
362
- }
363
- }
364
- },
365
- {
366
- type: "function",
367
- function: {
368
- name: "resolve_comment",
369
- description: "Mark a comment as resolved",
370
- parameters: {
371
- type: "object",
372
- properties: {
373
- doc_id: { type: "string", description: "Document ID" },
374
- comment_id: { type: "string", description: "ID of the comment to resolve" }
375
- },
376
- required: ["doc_id", "comment_id"]
377
- }
378
- }
379
- },
380
- {
381
- type: "function",
382
- function: {
383
- name: "delete_comment",
384
- description: "Delete a comment and its replies",
385
- parameters: {
386
- type: "object",
387
- properties: {
388
- doc_id: { type: "string", description: "Document ID" },
389
- comment_id: { type: "string", description: "ID of the comment to delete" }
390
- },
391
- required: ["doc_id", "comment_id"]
392
- }
393
- }
394
- },
395
- {
396
- type: "function",
397
- function: {
398
- name: "insert_paragraph",
399
- description: "Insert a new paragraph after existing text",
400
- parameters: {
401
- type: "object",
402
- properties: {
403
- doc_id: { type: "string", description: "Document ID" },
404
- after_text: { type: "string", description: "Text after which to insert the new paragraph" },
405
- new_text: { type: "string", description: "Content of the new paragraph" }
406
- },
407
- required: ["doc_id", "after_text", "new_text"]
408
- }
409
- }
410
- },
411
- {
412
- type: "function",
413
- function: {
414
- name: "validate_document",
415
- description: "Validate the document structure",
416
- parameters: {
417
- type: "object",
418
- properties: {
419
- doc_id: { type: "string", description: "Document ID" }
420
- },
421
- required: ["doc_id"]
422
- }
423
- }
424
- }
425
- ];
426
- var DocxAgent = class extends BaseAgent {
163
+ var DEFAULT_BASE_URL = "https://subagents.morphllm.com/v1";
164
+ var DocxAgent = class {
427
165
  constructor(options = {}) {
428
- const client = new DocxClient(options);
429
- super({ ...options, client });
166
+ this.baseUrl = options.baseUrl || process.env.MORPH_API_URL || DEFAULT_BASE_URL;
167
+ this.apiKey = options.apiKey || process.env.MORPH_API_KEY || "";
430
168
  this.documentId = options.documentId;
431
169
  }
432
- getDefaultModel() {
433
- return "moonshot-v1-32k";
434
- }
435
- getDefaultInstructions() {
436
- return this.documentId ? `${DEFAULT_INSTRUCTIONS}
437
-
438
- Current document ID: ${this.documentId}` : DEFAULT_INSTRUCTIONS;
439
- }
440
- getTools() {
441
- return TOOLS;
442
- }
443
170
  /** Set the current document ID */
444
171
  setDocument(docId) {
445
172
  this.documentId = docId;
@@ -448,100 +175,127 @@ Current document ID: ${this.documentId}` : DEFAULT_INSTRUCTIONS;
448
175
  getDocumentId() {
449
176
  return this.documentId;
450
177
  }
451
- async executeTool(name, args) {
452
- const docId = args.doc_id || this.documentId;
453
- if (!docId) {
454
- return "Error: No document ID provided. Please upload a document first.";
178
+ /**
179
+ * Send a chat completion request (non-streaming).
180
+ * This runs the full agent loop on the server and returns the final response.
181
+ */
182
+ async chat(messages) {
183
+ const contextMessages = this.documentId ? messages.map((m, i) => {
184
+ if (i === messages.length - 1 && m.role === "user") {
185
+ return {
186
+ ...m,
187
+ content: `[Document ID: ${this.documentId}]
188
+
189
+ ${m.content}`
190
+ };
191
+ }
192
+ return m;
193
+ }) : messages;
194
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ ...this.apiKey && { Authorization: `Bearer ${this.apiKey}` }
199
+ },
200
+ body: JSON.stringify({
201
+ model: "morph-docx",
202
+ messages: contextMessages,
203
+ stream: false
204
+ })
205
+ });
206
+ if (!response.ok) {
207
+ const error = await response.text();
208
+ throw new Error(`API error: ${error}`);
455
209
  }
456
- try {
457
- switch (name) {
458
- case "read_document": {
459
- const result = await this.client.read(docId);
460
- return `Document content (${result.paragraphs} paragraphs):
210
+ return response.json();
211
+ }
212
+ /**
213
+ * Send a streaming chat completion request.
214
+ * Returns an async generator that yields chunks as they arrive.
215
+ */
216
+ async *chatStream(messages) {
217
+ const contextMessages = this.documentId ? messages.map((m, i) => {
218
+ if (i === messages.length - 1 && m.role === "user") {
219
+ return {
220
+ ...m,
221
+ content: `[Document ID: ${this.documentId}]
461
222
 
462
- ${result.content}`;
463
- }
464
- case "add_comment": {
465
- const result = await this.client.edit(docId, [
466
- {
467
- type: "add_comment",
468
- para_text: args.para_text,
469
- comment: args.comment,
470
- highlight: args.highlight
471
- }
472
- ]);
473
- return result.results[0];
474
- }
475
- case "insert_text": {
476
- const result = await this.client.edit(docId, [
477
- {
478
- type: "insert_text",
479
- para_text: args.para_text,
480
- after: args.after,
481
- new_text: args.new_text
482
- }
483
- ]);
484
- return result.results[0];
485
- }
486
- case "propose_deletion": {
487
- const result = await this.client.edit(docId, [
488
- {
489
- type: "propose_deletion",
490
- para_text: args.para_text,
491
- target: args.target
492
- }
493
- ]);
494
- return result.results[0];
495
- }
496
- case "reply_comment": {
497
- const result = await this.client.edit(docId, [
498
- {
499
- type: "reply_comment",
500
- comment_id: args.comment_id,
501
- reply: args.reply
502
- }
503
- ]);
504
- return result.results[0];
505
- }
506
- case "resolve_comment": {
507
- const result = await this.client.edit(docId, [
508
- {
509
- type: "resolve_comment",
510
- comment_id: args.comment_id
511
- }
512
- ]);
513
- return result.results[0];
514
- }
515
- case "delete_comment": {
516
- const result = await this.client.edit(docId, [
517
- {
518
- type: "delete_comment",
519
- comment_id: args.comment_id
520
- }
521
- ]);
522
- return result.results[0];
523
- }
524
- case "insert_paragraph": {
525
- const result = await this.client.edit(docId, [
526
- {
527
- type: "insert_paragraph",
528
- after_text: args.after_text,
529
- new_text: args.new_text
530
- }
531
- ]);
532
- return result.results[0];
533
- }
534
- case "validate_document": {
535
- const result = await this.client.validate(docId);
536
- return `${result.result}: ${result.output}`;
223
+ ${m.content}`
224
+ };
225
+ }
226
+ return m;
227
+ }) : messages;
228
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
229
+ method: "POST",
230
+ headers: {
231
+ "Content-Type": "application/json",
232
+ ...this.apiKey && { Authorization: `Bearer ${this.apiKey}` }
233
+ },
234
+ body: JSON.stringify({
235
+ model: "morph-docx",
236
+ messages: contextMessages,
237
+ stream: true
238
+ })
239
+ });
240
+ if (!response.ok) {
241
+ const error = await response.text();
242
+ throw new Error(`API error: ${error}`);
243
+ }
244
+ const reader = response.body?.getReader();
245
+ if (!reader) {
246
+ throw new Error("No response body");
247
+ }
248
+ const decoder = new TextDecoder();
249
+ let buffer = "";
250
+ while (true) {
251
+ const { done, value } = await reader.read();
252
+ if (done) break;
253
+ buffer += decoder.decode(value, { stream: true });
254
+ const lines = buffer.split("\n");
255
+ buffer = lines.pop() || "";
256
+ for (const line of lines) {
257
+ if (!line.startsWith("data: ")) continue;
258
+ const data = line.slice(6).trim();
259
+ if (data === "[DONE]") return;
260
+ try {
261
+ const chunk = JSON.parse(data);
262
+ yield chunk;
263
+ } catch {
537
264
  }
538
- default:
539
- return `Error: Unknown tool '${name}'`;
540
265
  }
541
- } catch (error) {
542
- return `Error: ${error instanceof Error ? error.message : String(error)}`;
543
266
  }
544
267
  }
268
+ /**
269
+ * Helper: Simple completion that returns just the content string.
270
+ */
271
+ async complete(userMessage) {
272
+ const response = await this.chat([{ role: "user", content: userMessage }]);
273
+ return response.choices[0]?.message?.content || "";
274
+ }
275
+ /**
276
+ * Create an OpenAI-compatible client configuration.
277
+ * Use this with the OpenAI SDK or OpenAI Agents SDK.
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * import OpenAI from 'openai';
282
+ * import { DocxAgent } from '@morphllm/subagents/docx';
283
+ *
284
+ * const agent = new DocxAgent();
285
+ * const openai = new OpenAI(agent.getOpenAIConfig());
286
+ *
287
+ * const response = await openai.chat.completions.create({
288
+ * model: 'morph-docx',
289
+ * messages: [{ role: 'user', content: 'Hello!' }],
290
+ * });
291
+ * ```
292
+ */
293
+ getOpenAIConfig() {
294
+ return {
295
+ baseURL: this.baseUrl,
296
+ apiKey: this.apiKey || "not-required"
297
+ };
298
+ }
545
299
  };
546
300
 
547
301
  export { DocxAgent, DocxClient };