fourmis-agents-sdk 0.2.1 → 0.2.2

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/api.js CHANGED
@@ -2927,9 +2927,11 @@ class TaskManager {
2927
2927
  import { readFileSync as readFileSync4, appendFileSync, mkdirSync as mkdirSync3, readdirSync, statSync } from "fs";
2928
2928
  import { join as join4 } from "path";
2929
2929
  import { homedir as homedir2 } from "os";
2930
+ function sanitizeCwd(cwd) {
2931
+ return cwd.replace(/[/.]/g, "-");
2932
+ }
2930
2933
  function sessionsDir(cwd) {
2931
- const sanitized = cwd.replace(/^\//, "").replace(/\//g, "-");
2932
- return join4(homedir2(), ".claude", "projects", sanitized);
2934
+ return join4(homedir2(), ".claude", "projects", sanitizeCwd(cwd));
2933
2935
  }
2934
2936
  function ensureDir(dir) {
2935
2937
  mkdirSync3(dir, { recursive: true });
@@ -2940,11 +2942,15 @@ function logMessage(dir, sessionId, entry) {
2940
2942
  appendFileSync(filePath, JSON.stringify(entry) + `
2941
2943
  `);
2942
2944
  }
2943
- function createSessionLogger(cwd, sessionId) {
2945
+ function createSessionLogger(cwd, sessionId, model) {
2944
2946
  const dir = sessionsDir(cwd);
2945
2947
  let lastUuid = null;
2946
2948
  return (role, content, parentUuid) => {
2947
2949
  const entryUuid = uuid();
2950
+ let normalizedContent = content;
2951
+ if (role === "user" && typeof content === "string") {
2952
+ normalizedContent = [{ type: "text", text: content }];
2953
+ }
2948
2954
  const entry = {
2949
2955
  type: role,
2950
2956
  uuid: entryUuid,
@@ -2952,7 +2958,14 @@ function createSessionLogger(cwd, sessionId) {
2952
2958
  sessionId,
2953
2959
  timestamp: new Date().toISOString(),
2954
2960
  cwd,
2955
- message: { role, content }
2961
+ isSidechain: false,
2962
+ userType: "external",
2963
+ message: {
2964
+ role,
2965
+ content: normalizedContent,
2966
+ ...role === "assistant" && model ? { model } : {}
2967
+ },
2968
+ ...role === "user" ? { permissionMode: "default" } : {}
2956
2969
  };
2957
2970
  logMessage(dir, sessionId, entry);
2958
2971
  lastUuid = entryUuid;
@@ -2991,10 +3004,23 @@ function loadSessionMessages(cwd, sessionId) {
2991
3004
  for (const line of lines) {
2992
3005
  try {
2993
3006
  const entry = JSON.parse(line);
2994
- messages.push({
2995
- role: entry.type === "user" ? "user" : "assistant",
2996
- content: entry.message.content
2997
- });
3007
+ if (entry.type !== "user" && entry.type !== "assistant")
3008
+ continue;
3009
+ if (entry.isMeta === true)
3010
+ continue;
3011
+ const message = entry.message;
3012
+ if (!message)
3013
+ continue;
3014
+ const role = entry.type === "user" ? "user" : "assistant";
3015
+ let content;
3016
+ if (typeof message.content === "string") {
3017
+ content = message.content;
3018
+ } else if (Array.isArray(message.content)) {
3019
+ content = message.content.filter((c) => c.type === "text" || c.type === "tool_use" || c.type === "tool_result").map((c) => c);
3020
+ } else {
3021
+ continue;
3022
+ }
3023
+ messages.push({ role, content });
2998
3024
  } catch {}
2999
3025
  }
3000
3026
  return messages;
@@ -3064,7 +3090,7 @@ function query(params) {
3064
3090
  }
3065
3091
  }
3066
3092
  const persistSession = options.persistSession !== false;
3067
- const sessionLogger = persistSession ? createSessionLogger(cwd, sessionId) : undefined;
3093
+ const sessionLogger = persistSession ? createSessionLogger(cwd, sessionId, model) : undefined;
3068
3094
  const abortController = new AbortController;
3069
3095
  if (options.signal) {
3070
3096
  options.signal.addEventListener("abort", () => abortController.abort(), { once: true });
package/dist/index.js CHANGED
@@ -2927,9 +2927,11 @@ class TaskManager {
2927
2927
  import { readFileSync as readFileSync4, appendFileSync, mkdirSync as mkdirSync3, readdirSync, statSync } from "fs";
2928
2928
  import { join as join4 } from "path";
2929
2929
  import { homedir as homedir2 } from "os";
2930
+ function sanitizeCwd(cwd) {
2931
+ return cwd.replace(/[/.]/g, "-");
2932
+ }
2930
2933
  function sessionsDir(cwd) {
2931
- const sanitized = cwd.replace(/^\//, "").replace(/\//g, "-");
2932
- return join4(homedir2(), ".claude", "projects", sanitized);
2934
+ return join4(homedir2(), ".claude", "projects", sanitizeCwd(cwd));
2933
2935
  }
2934
2936
  function ensureDir(dir) {
2935
2937
  mkdirSync3(dir, { recursive: true });
@@ -2940,11 +2942,15 @@ function logMessage(dir, sessionId, entry) {
2940
2942
  appendFileSync(filePath, JSON.stringify(entry) + `
2941
2943
  `);
2942
2944
  }
2943
- function createSessionLogger(cwd, sessionId) {
2945
+ function createSessionLogger(cwd, sessionId, model) {
2944
2946
  const dir = sessionsDir(cwd);
2945
2947
  let lastUuid = null;
2946
2948
  return (role, content, parentUuid) => {
2947
2949
  const entryUuid = uuid();
2950
+ let normalizedContent = content;
2951
+ if (role === "user" && typeof content === "string") {
2952
+ normalizedContent = [{ type: "text", text: content }];
2953
+ }
2948
2954
  const entry = {
2949
2955
  type: role,
2950
2956
  uuid: entryUuid,
@@ -2952,7 +2958,14 @@ function createSessionLogger(cwd, sessionId) {
2952
2958
  sessionId,
2953
2959
  timestamp: new Date().toISOString(),
2954
2960
  cwd,
2955
- message: { role, content }
2961
+ isSidechain: false,
2962
+ userType: "external",
2963
+ message: {
2964
+ role,
2965
+ content: normalizedContent,
2966
+ ...role === "assistant" && model ? { model } : {}
2967
+ },
2968
+ ...role === "user" ? { permissionMode: "default" } : {}
2956
2969
  };
2957
2970
  logMessage(dir, sessionId, entry);
2958
2971
  lastUuid = entryUuid;
@@ -2991,10 +3004,23 @@ function loadSessionMessages(cwd, sessionId) {
2991
3004
  for (const line of lines) {
2992
3005
  try {
2993
3006
  const entry = JSON.parse(line);
2994
- messages.push({
2995
- role: entry.type === "user" ? "user" : "assistant",
2996
- content: entry.message.content
2997
- });
3007
+ if (entry.type !== "user" && entry.type !== "assistant")
3008
+ continue;
3009
+ if (entry.isMeta === true)
3010
+ continue;
3011
+ const message = entry.message;
3012
+ if (!message)
3013
+ continue;
3014
+ const role = entry.type === "user" ? "user" : "assistant";
3015
+ let content;
3016
+ if (typeof message.content === "string") {
3017
+ content = message.content;
3018
+ } else if (Array.isArray(message.content)) {
3019
+ content = message.content.filter((c) => c.type === "text" || c.type === "tool_use" || c.type === "tool_result").map((c) => c);
3020
+ } else {
3021
+ continue;
3022
+ }
3023
+ messages.push({ role, content });
2998
3024
  } catch {}
2999
3025
  }
3000
3026
  return messages;
@@ -3064,7 +3090,7 @@ function query(params) {
3064
3090
  }
3065
3091
  }
3066
3092
  const persistSession = options.persistSession !== false;
3067
- const sessionLogger = persistSession ? createSessionLogger(cwd, sessionId) : undefined;
3093
+ const sessionLogger = persistSession ? createSessionLogger(cwd, sessionId, model) : undefined;
3068
3094
  const abortController = new AbortController;
3069
3095
  if (options.signal) {
3070
3096
  options.signal.addEventListener("abort", () => abortController.abort(), { once: true });
@@ -3,8 +3,18 @@
3
3
  *
4
4
  * Sessions are stored as one JSON object per line in:
5
5
  * ~/.claude/projects/{sanitized-cwd}/{sessionId}.jsonl
6
+ *
7
+ * Path sanitization matches Claude SDK: replace `/` and `.` with `-`.
8
+ * /root/.fourmis/workspaces/doremi → -root--fourmis-workspaces-doremi
6
9
  */
7
10
  import type { NormalizedMessage, NormalizedContent } from "../providers/types.js";
11
+ /**
12
+ * JSONL entry — compatible with Claude SDK session format.
13
+ *
14
+ * Claude SDK entries include additional fields (isSidechain, userType, version,
15
+ * gitBranch, permissionMode, message.model, message.usage, requestId, etc.)
16
+ * that we include for compatibility. When loading, we only need type + message.
17
+ */
8
18
  export type SessionEntry = {
9
19
  type: "user" | "assistant";
10
20
  uuid: string;
@@ -12,11 +22,22 @@ export type SessionEntry = {
12
22
  sessionId: string;
13
23
  timestamp: string;
14
24
  cwd: string;
25
+ isSidechain: boolean;
26
+ userType: string;
15
27
  message: {
16
28
  role: string;
17
29
  content: NormalizedContent[] | string;
30
+ model?: string;
18
31
  };
32
+ permissionMode?: string;
19
33
  };
34
+ /**
35
+ * Sanitize a cwd path to a directory name, matching Claude SDK convention.
36
+ * Replaces `/` and `.` with `-`.
37
+ * /root/dev/fourmis → -root-dev-fourmis
38
+ * /root/.fourmis/workspaces/doremi → -root--fourmis-workspaces-doremi
39
+ */
40
+ export declare function sanitizeCwd(cwd: string): string;
20
41
  /**
21
42
  * Returns the sessions directory for a given cwd.
22
43
  * Path: ~/.claude/projects/{sanitized-cwd}/
@@ -28,8 +49,9 @@ export declare function sessionsDir(cwd: string): string;
28
49
  export declare function logMessage(dir: string, sessionId: string, entry: SessionEntry): void;
29
50
  /**
30
51
  * Create a session logger function for use in the agent loop.
52
+ * Returns a function that logs messages and returns the entry UUID.
31
53
  */
32
- export declare function createSessionLogger(cwd: string, sessionId: string): (role: "user" | "assistant", content: NormalizedContent[] | string, parentUuid: string | null) => string;
54
+ export declare function createSessionLogger(cwd: string, sessionId: string, model?: string): (role: "user" | "assistant", content: NormalizedContent[] | string, parentUuid: string | null) => string;
33
55
  /**
34
56
  * Find the most recent session file in the sessions directory for a cwd.
35
57
  * Returns the sessionId (filename without .jsonl) or null.
@@ -37,6 +59,7 @@ export declare function createSessionLogger(cwd: string, sessionId: string): (ro
37
59
  export declare function findLatestSession(cwd: string): string | null;
38
60
  /**
39
61
  * Load messages from a session JSONL file and reconstruct NormalizedMessage[].
62
+ * Skips non-message entries (file-history-snapshot, queue-operation, system, progress).
40
63
  */
41
64
  export declare function loadSessionMessages(cwd: string, sessionId: string): NormalizedMessage[];
42
65
  //# sourceMappingURL=session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/utils/session-store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAKlF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC;KACvC,CAAC;CACH,CAAC;AAIF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAWD;;GAEG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,GAClB,IAAI,CAIN;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAmB1G;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,iBAAiB,EAAE,CA0BrB"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/utils/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAKlF;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC;QACtC,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAIF;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAWD;;GAEG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,YAAY,GAClB,IAAI,CAIN;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAkC1G;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB5D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,iBAAiB,EAAE,CAgDrB"}
@@ -37,9 +37,11 @@ function mergeUsage(a, b) {
37
37
  import { readFileSync, appendFileSync, mkdirSync, readdirSync, statSync } from "fs";
38
38
  import { join } from "path";
39
39
  import { homedir } from "os";
40
+ function sanitizeCwd(cwd) {
41
+ return cwd.replace(/[/.]/g, "-");
42
+ }
40
43
  function sessionsDir(cwd) {
41
- const sanitized = cwd.replace(/^\//, "").replace(/\//g, "-");
42
- return join(homedir(), ".claude", "projects", sanitized);
44
+ return join(homedir(), ".claude", "projects", sanitizeCwd(cwd));
43
45
  }
44
46
  function ensureDir(dir) {
45
47
  mkdirSync(dir, { recursive: true });
@@ -50,11 +52,15 @@ function logMessage(dir, sessionId, entry) {
50
52
  appendFileSync(filePath, JSON.stringify(entry) + `
51
53
  `);
52
54
  }
53
- function createSessionLogger(cwd, sessionId) {
55
+ function createSessionLogger(cwd, sessionId, model) {
54
56
  const dir = sessionsDir(cwd);
55
57
  let lastUuid = null;
56
58
  return (role, content, parentUuid) => {
57
59
  const entryUuid = uuid();
60
+ let normalizedContent = content;
61
+ if (role === "user" && typeof content === "string") {
62
+ normalizedContent = [{ type: "text", text: content }];
63
+ }
58
64
  const entry = {
59
65
  type: role,
60
66
  uuid: entryUuid,
@@ -62,7 +68,14 @@ function createSessionLogger(cwd, sessionId) {
62
68
  sessionId,
63
69
  timestamp: new Date().toISOString(),
64
70
  cwd,
65
- message: { role, content }
71
+ isSidechain: false,
72
+ userType: "external",
73
+ message: {
74
+ role,
75
+ content: normalizedContent,
76
+ ...role === "assistant" && model ? { model } : {}
77
+ },
78
+ ...role === "user" ? { permissionMode: "default" } : {}
66
79
  };
67
80
  logMessage(dir, sessionId, entry);
68
81
  lastUuid = entryUuid;
@@ -101,16 +114,30 @@ function loadSessionMessages(cwd, sessionId) {
101
114
  for (const line of lines) {
102
115
  try {
103
116
  const entry = JSON.parse(line);
104
- messages.push({
105
- role: entry.type === "user" ? "user" : "assistant",
106
- content: entry.message.content
107
- });
117
+ if (entry.type !== "user" && entry.type !== "assistant")
118
+ continue;
119
+ if (entry.isMeta === true)
120
+ continue;
121
+ const message = entry.message;
122
+ if (!message)
123
+ continue;
124
+ const role = entry.type === "user" ? "user" : "assistant";
125
+ let content;
126
+ if (typeof message.content === "string") {
127
+ content = message.content;
128
+ } else if (Array.isArray(message.content)) {
129
+ content = message.content.filter((c) => c.type === "text" || c.type === "tool_use" || c.type === "tool_result").map((c) => c);
130
+ } else {
131
+ continue;
132
+ }
133
+ messages.push({ role, content });
108
134
  } catch {}
109
135
  }
110
136
  return messages;
111
137
  }
112
138
  export {
113
139
  sessionsDir,
140
+ sanitizeCwd,
114
141
  logMessage,
115
142
  loadSessionMessages,
116
143
  findLatestSession,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fourmis-agents-sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Multi-provider AI agent SDK with direct API access and in-process tool execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",