@coffer-org/plugin-claude-agent 2.4.0 → 2.5.0
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.
- package/dist/runtime/agent.js +20 -3
- package/dist/runtime/coffer.d.ts +2 -1
- package/dist/runtime/coffer.js +11 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/inspect-attachment.d.ts +27 -0
- package/dist/runtime/inspect-attachment.js +65 -0
- package/dist/runtime/types.d.ts +7 -0
- package/dist/schema.js +23 -30
- package/package.json +4 -4
package/dist/runtime/agent.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import Anthropic from '@anthropic-ai/sdk';
|
|
3
3
|
import { effortParam, loadAgentConfig, modelId } from "./config.js";
|
|
4
4
|
import { makeAgentTools } from "./coffer.js";
|
|
5
|
+
import { isAgentToolContentResult } from "./inspect-attachment.js";
|
|
5
6
|
import { buildDomainSections } from '@coffer-org/server/mcp-tools';
|
|
6
7
|
import { isTracing, flushTracing, langfuseFactory, TurnTracer } from "./tracing.js";
|
|
7
8
|
import { getLogger } from '@coffer-org/sdk/logger';
|
|
@@ -63,7 +64,12 @@ export function renderContent(m) {
|
|
|
63
64
|
if (m.role !== 'user')
|
|
64
65
|
return m.content;
|
|
65
66
|
const author = m.sender ? `<author>${esc(m.sender)}</author>\n` : '';
|
|
66
|
-
|
|
67
|
+
const attachments = m.attachments?.length
|
|
68
|
+
? `\n<attachments>\n${m.attachments
|
|
69
|
+
.map((a) => `- name=${esc(a.name)} mime=${esc(a.mime ?? 'unknown')} size=${a.size ?? 'unknown'}${a.label ? ` label=${esc(a.label)}` : ''}`)
|
|
70
|
+
.join('\n')}\n</attachments>`
|
|
71
|
+
: '';
|
|
72
|
+
return `${author}<body>${esc(m.content)}${attachments}</body>`;
|
|
67
73
|
}
|
|
68
74
|
export const MAX_TOOL_RESULT_CHARS = 100_000;
|
|
69
75
|
export function capToolResult(text) {
|
|
@@ -129,7 +135,8 @@ export async function runAgent(request, deps = {}) {
|
|
|
129
135
|
try {
|
|
130
136
|
const client = makeClientFn(cfg);
|
|
131
137
|
const rag = cfg.ragEnabled && cfg.embeddingApiKey ? { embeddingApiKey: cfg.embeddingApiKey, topK: cfg.ragTopK } : null;
|
|
132
|
-
const
|
|
138
|
+
const attachmentRefs = request.messages.flatMap((m) => m.attachments ?? []);
|
|
139
|
+
const { tools, handlers } = await makeAgentToolsFn(rag, attachmentRefs);
|
|
133
140
|
const system = request.system.map((l) => ({
|
|
134
141
|
type: 'text',
|
|
135
142
|
text: l.text,
|
|
@@ -244,7 +251,17 @@ export async function runAgent(request, deps = {}) {
|
|
|
244
251
|
}
|
|
245
252
|
else {
|
|
246
253
|
try {
|
|
247
|
-
|
|
254
|
+
const toolValue = await handler(b.input);
|
|
255
|
+
if (isAgentToolContentResult(toolValue)) {
|
|
256
|
+
results.push({
|
|
257
|
+
type: 'tool_result',
|
|
258
|
+
tool_use_id: b.id,
|
|
259
|
+
content: toolValue.content,
|
|
260
|
+
...(toolValue.isError ? { is_error: true } : {}),
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
content = JSON.stringify(toolValue ?? null);
|
|
248
265
|
}
|
|
249
266
|
catch (err) {
|
|
250
267
|
content = `Tool error: ${err instanceof Error ? err.message : String(err)}`;
|
package/dist/runtime/coffer.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type RagDeps } from '@coffer-org/server/mcp-tools';
|
|
2
|
+
import type { AttachmentRef } from './types.ts';
|
|
2
3
|
export interface AgentTool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
5
6
|
input_schema: Record<string, unknown>;
|
|
6
7
|
}
|
|
7
8
|
export type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;
|
|
8
|
-
export declare function makeAgentTools(rag: RagDeps | null): Promise<{
|
|
9
|
+
export declare function makeAgentTools(rag: RagDeps | null, attachments?: AttachmentRef[]): Promise<{
|
|
9
10
|
tools: AgentTool[];
|
|
10
11
|
handlers: Record<string, ToolHandler>;
|
|
11
12
|
}>;
|
package/dist/runtime/coffer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { collectMcpTools } from '@coffer-org/server/mcp-tools';
|
|
3
|
-
|
|
3
|
+
import { inspectAttachment } from "./inspect-attachment.js";
|
|
4
|
+
export async function makeAgentTools(rag, attachments = []) {
|
|
4
5
|
const defs = await collectMcpTools({ rag });
|
|
5
6
|
const tools = [];
|
|
6
7
|
const handlers = {};
|
|
@@ -13,5 +14,14 @@ export async function makeAgentTools(rag) {
|
|
|
13
14
|
});
|
|
14
15
|
handlers[name] = (args) => d.handler(args);
|
|
15
16
|
}
|
|
17
|
+
if (attachments.length) {
|
|
18
|
+
const name = 'mcp__coffer__inspect_attachment';
|
|
19
|
+
tools.push({
|
|
20
|
+
name,
|
|
21
|
+
description: 'Inspect an uploaded attachment from the current conversation. This is expensive and exposes file content to the model; call it only when the user asks what is in the file or when seeing the image is necessary. For simply assigning the file to a record, use its name without this tool.',
|
|
22
|
+
input_schema: z.toJSONSchema(z.object({ name: z.string().describe('Exact server-generated attachment name') })),
|
|
23
|
+
});
|
|
24
|
+
handlers[name] = async (args) => inspectAttachment(String(args['name'] ?? ''), attachments);
|
|
25
|
+
}
|
|
16
26
|
return { tools, handlers };
|
|
17
27
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PluginHooks } from '@coffer-org/server/plugin-hooks';
|
|
2
2
|
export { runAgent, agentSystemBase } from './agent.ts';
|
|
3
3
|
export type { AgentRunDeps, AgentTracer } from './agent.ts';
|
|
4
|
-
export type { ConvMessage, SystemLayer, AgentRequest, AgentResult } from './types.ts';
|
|
4
|
+
export type { AttachmentRef, ConvMessage, SystemLayer, AgentRequest, AgentResult } from './types.ts';
|
|
5
5
|
export declare const serverHooks: PluginHooks;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AttachmentRef } from './types.ts';
|
|
2
|
+
type ContentBlock = {
|
|
3
|
+
type: 'text';
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'image';
|
|
7
|
+
source: {
|
|
8
|
+
type: 'base64';
|
|
9
|
+
media_type: string;
|
|
10
|
+
data: string;
|
|
11
|
+
};
|
|
12
|
+
} | {
|
|
13
|
+
type: 'document';
|
|
14
|
+
source: {
|
|
15
|
+
type: 'base64';
|
|
16
|
+
media_type: 'application/pdf';
|
|
17
|
+
data: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export interface AgentToolContentResult {
|
|
21
|
+
__cofferToolContent: true;
|
|
22
|
+
content: ContentBlock[];
|
|
23
|
+
isError?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function isAgentToolContentResult(value: unknown): value is AgentToolContentResult;
|
|
26
|
+
export declare function inspectAttachment(name: string, attachments: AttachmentRef[]): Promise<AgentToolContentResult>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { mimeForName } from '@coffer-org/server/file-fields';
|
|
4
|
+
import { uploadsDir } from '@coffer-org/server/uploads';
|
|
5
|
+
const MAX_VISION_BYTES = 5 * 1024 * 1024;
|
|
6
|
+
const MAX_DOCUMENT_BYTES = 10 * 1024 * 1024;
|
|
7
|
+
const MAX_TEXT_BYTES = 512 * 1024;
|
|
8
|
+
export function isAgentToolContentResult(value) {
|
|
9
|
+
return typeof value === 'object' && value !== null && value.__cofferToolContent === true;
|
|
10
|
+
}
|
|
11
|
+
const textResult = (text, isError = false) => ({
|
|
12
|
+
__cofferToolContent: true,
|
|
13
|
+
content: [{ type: 'text', text }],
|
|
14
|
+
...(isError ? { isError: true } : {}),
|
|
15
|
+
});
|
|
16
|
+
export async function inspectAttachment(name, attachments) {
|
|
17
|
+
const ref = attachments.find((a) => a.name === name);
|
|
18
|
+
if (!ref)
|
|
19
|
+
return textResult(`Attachment is not available in this conversation: ${name}`, true);
|
|
20
|
+
if (basename(name) !== name)
|
|
21
|
+
return textResult('Invalid attachment name.', true);
|
|
22
|
+
const file = join(uploadsDir(), name);
|
|
23
|
+
let size;
|
|
24
|
+
try {
|
|
25
|
+
size = (await stat(file)).size;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return textResult(`Attachment is no longer present on the server: ${name}`, true);
|
|
29
|
+
}
|
|
30
|
+
const mime = ref.mime ?? mimeForName(name);
|
|
31
|
+
if (mime === 'image/jpeg' || mime === 'image/png' || mime === 'image/gif' || mime === 'image/webp') {
|
|
32
|
+
if (size > MAX_VISION_BYTES) {
|
|
33
|
+
return textResult(`Image is ${size} bytes, above the ${MAX_VISION_BYTES}-byte inspection limit. It can still be attached to a record.`, true);
|
|
34
|
+
}
|
|
35
|
+
const bytes = await readFile(file);
|
|
36
|
+
return {
|
|
37
|
+
__cofferToolContent: true,
|
|
38
|
+
content: [
|
|
39
|
+
{ type: 'text', text: `Attached image: ${ref.label ?? name} (${mime}, ${size} bytes).` },
|
|
40
|
+
{ type: 'image', source: { type: 'base64', media_type: mime, data: bytes.toString('base64') } },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (mime === 'application/pdf') {
|
|
45
|
+
if (size > MAX_DOCUMENT_BYTES) {
|
|
46
|
+
return textResult(`PDF is ${size} bytes, above the ${MAX_DOCUMENT_BYTES}-byte inspection limit.`, true);
|
|
47
|
+
}
|
|
48
|
+
const bytes = await readFile(file);
|
|
49
|
+
return {
|
|
50
|
+
__cofferToolContent: true,
|
|
51
|
+
content: [
|
|
52
|
+
{ type: 'text', text: `Attached PDF: ${ref.label ?? name} (${size} bytes).` },
|
|
53
|
+
{ type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: bytes.toString('base64') } },
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (mime?.startsWith('text/') || mime === 'application/json' || mime === 'text/csv') {
|
|
58
|
+
if (size > MAX_TEXT_BYTES)
|
|
59
|
+
return textResult(`Text attachment is too large to inspect: ${size} bytes.`, true);
|
|
60
|
+
const text = (await readFile(file)).toString('utf8');
|
|
61
|
+
return textResult(`Attached text file: ${ref.label ?? name}\n\n${text}`);
|
|
62
|
+
}
|
|
63
|
+
return textResult(`This attachment type cannot be inspected by the agent (${mime ?? 'unknown'}). ` +
|
|
64
|
+
'It can still be assigned to a file/media field by its name.', true);
|
|
65
|
+
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export interface AttachmentRef {
|
|
2
|
+
name: string;
|
|
3
|
+
mime?: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
1
7
|
export interface ConvMessage {
|
|
2
8
|
role: 'user' | 'assistant';
|
|
3
9
|
content: string;
|
|
10
|
+
attachments?: AttachmentRef[];
|
|
4
11
|
sender?: string | null;
|
|
5
12
|
msgId: string;
|
|
6
13
|
ts: number;
|
package/dist/schema.js
CHANGED
|
@@ -4391,11 +4391,11 @@ function resolveUnits(u) {
|
|
|
4391
4391
|
//#endregion
|
|
4392
4392
|
//#region ../sdk/src/currencies.ts
|
|
4393
4393
|
/**
|
|
4394
|
-
*
|
|
4395
|
-
*
|
|
4394
|
+
* Currencies from built-in `Intl` (ISO 4217), with NO hand list or npm dependency.
|
|
4395
|
+
* Codes come from `Intl.supportedValuesOf('currency')`; symbols/names are localized through
|
|
4396
4396
|
* `Intl.NumberFormat`/`Intl.DisplayNames`.
|
|
4397
4397
|
*/
|
|
4398
|
-
/**
|
|
4398
|
+
/** All ISO 4217 codes. Fallback: several major codes for older runtimes. */
|
|
4399
4399
|
var CURRENCY_CODES = (() => {
|
|
4400
4400
|
try {
|
|
4401
4401
|
return Intl.supportedValuesOf("currency");
|
|
@@ -4410,26 +4410,26 @@ var CURRENCY_CODES = (() => {
|
|
|
4410
4410
|
}
|
|
4411
4411
|
})();
|
|
4412
4412
|
var CODE_SET = new Set(CURRENCY_CODES);
|
|
4413
|
-
/**
|
|
4413
|
+
/** Whether an ISO 4217 code is valid. */
|
|
4414
4414
|
var isCurrencyCode = (c) => CODE_SET.has(c);
|
|
4415
4415
|
//#endregion
|
|
4416
4416
|
//#region ../sdk/src/fields/validation.ts
|
|
4417
|
-
/**
|
|
4417
|
+
/** Structured message for zod: JSON {code, params}. Decoded by mutate.ts. */
|
|
4418
4418
|
function vmsg(code, params) {
|
|
4419
4419
|
return JSON.stringify(params ? {
|
|
4420
4420
|
code,
|
|
4421
4421
|
params
|
|
4422
4422
|
} : { code });
|
|
4423
4423
|
}
|
|
4424
|
-
/** v4 error-map:
|
|
4424
|
+
/** v4 error-map: message for a missing value (formerly required_error). */
|
|
4425
4425
|
function reqErr(code = "required") {
|
|
4426
4426
|
return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
|
|
4427
4427
|
}
|
|
4428
|
-
/** v4 error-map:
|
|
4428
|
+
/** v4 error-map: message for an invalid type (formerly invalid_type_error). */
|
|
4429
4429
|
function typeErr(code = "invalid_type") {
|
|
4430
4430
|
return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
|
|
4431
4431
|
}
|
|
4432
|
-
/** v4 error-map: required + invalid_type
|
|
4432
|
+
/** v4 error-map: required + invalid_type together (formerly required_error + invalid_type_error). */
|
|
4433
4433
|
function reqTypeErr() {
|
|
4434
4434
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4435
4435
|
}
|
|
@@ -4447,10 +4447,10 @@ function jsonValue(raw) {
|
|
|
4447
4447
|
return raw;
|
|
4448
4448
|
}
|
|
4449
4449
|
/**
|
|
4450
|
-
*
|
|
4451
|
-
*
|
|
4452
|
-
* Single
|
|
4453
|
-
*
|
|
4450
|
+
* Factory for fields that store JSON and validate it with a nested zod schema.
|
|
4451
|
+
* Accepts a native object/array (native form state) OR a JSON string (legacy).
|
|
4452
|
+
* Single parse through jsonValue → inner.safeParse → issue `code`; an unparseable
|
|
4453
|
+
* string remains a string → code 'json'.
|
|
4454
4454
|
*/
|
|
4455
4455
|
function jsonRefined(inner, code) {
|
|
4456
4456
|
return unknown().superRefine((raw, ctx) => {
|
|
@@ -4522,16 +4522,16 @@ function normalizeOpts(rawIn) {
|
|
|
4522
4522
|
//#endregion
|
|
4523
4523
|
//#region ../sdk/src/field-presets.ts
|
|
4524
4524
|
/**
|
|
4525
|
-
*
|
|
4525
|
+
* Field presets are thin wrappers around the primitives in fields.ts.
|
|
4526
4526
|
*
|
|
4527
|
-
*
|
|
4528
|
-
*
|
|
4527
|
+
* Each preset = one kind (one widget). Presets do not accept `format`;
|
|
4528
|
+
* they are semantic types themselves. min/max/step go through `config`.
|
|
4529
4529
|
*
|
|
4530
|
-
*
|
|
4531
|
-
*
|
|
4530
|
+
* Presets are added to `f` through `composeF`, which guarantees no preset
|
|
4531
|
+
* overrides a primitive.
|
|
4532
4532
|
*
|
|
4533
|
-
*
|
|
4534
|
-
*
|
|
4533
|
+
* The cyclic import from fields.ts is safe: factories/helpers are hoisted declarations,
|
|
4534
|
+
* and presets call them only inside their function bodies.
|
|
4535
4535
|
*/
|
|
4536
4536
|
function email(raw) {
|
|
4537
4537
|
const o = normalizeOpts(raw);
|
|
@@ -4705,9 +4705,9 @@ function link(raw) {
|
|
|
4705
4705
|
}, o.multiple ?? false));
|
|
4706
4706
|
}
|
|
4707
4707
|
var TEL_RE = /^\+?[\d\s()-]{4,}$/;
|
|
4708
|
-
/** Loose URL:
|
|
4708
|
+
/** Loose URL: any scheme:// OR dotted host (optional port/path). No spaces. */
|
|
4709
4709
|
var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
|
|
4710
|
-
/** CSS named colors (CSS Color Module L4)
|
|
4710
|
+
/** CSS named colors (CSS Color Module L4) for f.colorname. */
|
|
4711
4711
|
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
4712
4712
|
"aliceblue",
|
|
4713
4713
|
"antiquewhite",
|
|
@@ -4966,7 +4966,7 @@ function reminder(raw) {
|
|
|
4966
4966
|
}
|
|
4967
4967
|
var _real = real;
|
|
4968
4968
|
var _int = int;
|
|
4969
|
-
/**
|
|
4969
|
+
/** Percentage 0..100 — real with rules:{min:0,max:100}. */
|
|
4970
4970
|
function percent(o) {
|
|
4971
4971
|
return _real({
|
|
4972
4972
|
...o,
|
|
@@ -4977,7 +4977,7 @@ function percent(o) {
|
|
|
4977
4977
|
}
|
|
4978
4978
|
});
|
|
4979
4979
|
}
|
|
4980
|
-
/**
|
|
4980
|
+
/** Year — int with rules:{min:1900,max:2100}; bounds can be overridden via rules.min/max. */
|
|
4981
4981
|
function year(o) {
|
|
4982
4982
|
return _int({
|
|
4983
4983
|
...o,
|
|
@@ -6027,13 +6027,6 @@ function wrapKey(opts, meta) {
|
|
|
6027
6027
|
role: opts.role
|
|
6028
6028
|
}
|
|
6029
6029
|
};
|
|
6030
|
-
if (opts.pinned) m = {
|
|
6031
|
-
...m,
|
|
6032
|
-
hints: {
|
|
6033
|
-
...m.hints,
|
|
6034
|
-
pinned: true
|
|
6035
|
-
}
|
|
6036
|
-
};
|
|
6037
6030
|
if (opts.default !== void 0) m = {
|
|
6038
6031
|
...m,
|
|
6039
6032
|
default: opts.default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffer-org/plugin-claude-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@anthropic-ai/sdk": "^0.111.0",
|
|
29
|
-
"@coffer-org/mcp": "^2.3.
|
|
30
|
-
"@coffer-org/sdk": "^2.1.
|
|
31
|
-
"@coffer-org/server": "^2.
|
|
29
|
+
"@coffer-org/mcp": "^2.3.1",
|
|
30
|
+
"@coffer-org/sdk": "^2.1.1",
|
|
31
|
+
"@coffer-org/server": "^2.3.0",
|
|
32
32
|
"@langfuse/otel": "^4.6.1",
|
|
33
33
|
"@langfuse/tracing": "^4.6.1",
|
|
34
34
|
"@opentelemetry/sdk-trace-node": "^2.8.0",
|