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