sage-mem 0.4.0 → 0.4.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/lib/index.js CHANGED
@@ -1,212 +1,212 @@
1
- /**
2
- * sage-mem — 文件式记忆插件(CC 原生方式)。
3
- *
4
- * 记忆存在本地 markdown 文件目录(frontmatter + 正文,4 类),不是数据库。
5
- * host 侧两件事:
6
- * 1. 按问题检索注入:system-prompt/assemble 时扫 memory 目录,按双字匹配
7
- * 选相关文件,读全文注入 system prompt,让 agent 第一轮就「想起」
8
- * 2. TypertRemoteService:给设置页「记忆管理」提供文件列表/读/写/删
9
- *
10
- * 写入靠 AGENTS.md 的「记忆使用」规则引导 agent 自己判断 + 用文件工具写
11
- * memory 目录——透明、可检查、防膨胀。无 worker、无 SQLite、无端口。
12
- *
13
- * 旧 worker 版见 git 分支 `sqlite-worker`。
14
- */
15
-
16
- import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
17
- import { homedir } from 'node:os'
18
- import { readFile, readdir, writeFile, unlink } from 'node:fs/promises'
19
- import { join, basename } from 'node:path'
20
-
21
- const MEMORY_DIR = process.env.SAGE_MEM_DIR || join(homedir(), '.sage-mem', 'memory')
22
- const MAX_RESULTS = 5
23
- const MAX_CHARS_PER_FILE = 1500
24
-
25
- /** 提取消息文本,兼容 content 为字符串或 text-block 数组两种形态。 */
26
- function extractText(content) {
27
- if (typeof content === 'string') return content.trim()
28
- if (!Array.isArray(content)) return ''
29
- return content
30
- .filter(b => b && b.type === 'text' && typeof b.text === 'string')
31
- .map(b => b.text)
32
- .join('\n')
33
- .trim()
34
- }
35
-
36
- /** 提取双字(2-gram)集合,中英文数字通用。 */
37
- function bigrams(s) {
38
- const clean = String(s).toLowerCase().replace(/[^\p{L}\p{N}]+/gu, '')
39
- const set = new Set()
40
- for (let i = 0; i < clean.length - 1; i++) set.add(clean.slice(i, i + 2))
41
- return set
42
- }
43
-
44
- /** query 的双字在 text 中的覆盖率(0~1),作为相关性得分。 */
45
- function score(query, text) {
46
- const q = bigrams(query)
47
- if (q.size === 0) return 0
48
- const t = bigrams(text)
49
- let hit = 0
50
- for (const g of q) if (t.has(g)) hit++
51
- return hit / q.size
52
- }
53
-
54
- /** 扫 memory 目录,读回全部记忆文件的正文(跳过索引与子目录)。 */
55
- async function scanMemoryFiles() {
56
- try {
57
- const entries = await readdir(MEMORY_DIR, { withFileTypes: true })
58
- const names = entries
59
- .filter(e => e.isFile() && e.name.endsWith('.md') && e.name !== 'MEMORY.md')
60
- .map(e => e.name)
61
- const loaded = await Promise.all(names.map(async (name) => {
62
- try {
63
- const content = await readFile(join(MEMORY_DIR, name), 'utf8')
64
- return { file: name, content }
65
- } catch {
66
- return null
67
- }
68
- }))
69
- return loaded.filter(Boolean)
70
- } catch {
71
- return []
72
- }
73
- }
74
-
75
- /** 按得分选相关文件,降序取 top N。 */
76
- function selectRelevant(query, files) {
77
- return files
78
- .map(f => ({ ...f, score: score(query, f.content) }))
79
- .filter(f => f.score > 0)
80
- .sort((a, b) => b.score - a.score)
81
- .slice(0, MAX_RESULTS)
82
- }
83
-
84
- /** 解析 frontmatter 的 name/description/type(兼容顶层与 metadata 嵌套两种 type)。 */
85
- function parseFrontmatter(content) {
86
- const m = content.match(/^---\n([\s\S]*?)\n---/)
87
- if (!m) return { name: '', description: '', type: '' }
88
- const body = m[1]
89
- const grab = (key) => {
90
- const hit = body.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))
91
- if (!hit) return ''
92
- return hit[1].replace(/^["']|["']$/g, '').trim()
93
- }
94
- let type = grab('type')
95
- if (!type) {
96
- const nested = body.match(/^metadata:\s*\n(?:\s+[^\n]+\n)*?\s+type:\s*(\S+)/m)
97
- if (nested) type = nested[1]
98
- }
99
- return { name: grab('name'), description: grab('description'), type }
100
- }
101
-
102
- /** 防路径穿越:只取 basename,且必须在 memory 目录内(.md,排除索引)。 */
103
- function safeName(raw) {
104
- const name = basename(String(raw || ''))
105
- if (!name || !name.endsWith('.md') || name === 'MEMORY.md') return null
106
- return name
107
- }
108
-
109
- /**
110
- * 手动 @Remote marker 注册(Node 24 不解析装饰器语法,手写 decorator context)。
111
- */
112
- function markRemote(cls, method, exportName) {
113
- const instance = Object.create(cls.prototype)
114
- Remote(exportName)(undefined, {
115
- kind: 'method',
116
- name: method,
117
- private: false,
118
- static: false,
119
- addInitializer(fn) {
120
- fn.call(instance)
121
- },
122
- })
123
- }
124
-
125
- export class MemoryGateway extends TypertRemoteService {
126
- constructor(ctx) {
127
- super(ctx, 'memory')
128
-
129
- // 按问题检索注入:system-prompt/assemble 是异步 waterfall,监听器可 await。
130
- // 从 agent.session 拿当前 user message,扫 memory 目录按双字匹配选相关
131
- // 文件,读全文作为动态 context 段注入——让 agent 第一轮就「想起」。
132
- ctx.on('system-prompt/assemble', async (assembly, context, next) => {
133
- const assembled = await next()
134
- try {
135
- const agent = context?.agent
136
- const session = agent?.session
137
- if (!session || typeof session.deriveMessages !== 'function') return assembled
138
- const messages = [...session.deriveMessages()]
139
- const lastUser = messages.reverse().find(m => m?.role === 'user')
140
- const text = extractText(lastUser?.content)
141
- if (!text || text.length < 2) return assembled
142
-
143
- const files = await scanMemoryFiles()
144
- const relevant = selectRelevant(text, files)
145
- if (relevant.length === 0) return assembled
146
-
147
- const parts = relevant.map(f => {
148
- const body = f.content.length > MAX_CHARS_PER_FILE
149
- ? f.content.slice(0, MAX_CHARS_PER_FILE) + '\n…(截断)'
150
- : f.content
151
- return `### ${f.file}\n${body}`
152
- })
153
-
154
- return {
155
- ...assembled,
156
- contexts: [...(assembled.contexts ?? []), {
157
- name: 'sage-mem:recall',
158
- order: 1000,
159
- text: `以下是与你当前问题相关的历史记忆(文件式,来自 ${MEMORY_DIR}):\n\n${parts.join('\n\n')}`,
160
- }],
161
- }
162
- } catch {
163
- return assembled
164
- }
165
- })
166
- }
167
-
168
- /** 列出 memory 目录全部记忆文件(文件名 + 类型 + 描述 + 大小)。 */
169
- async listFiles() {
170
- const files = await scanMemoryFiles()
171
- return files.map(f => {
172
- const meta = parseFrontmatter(f.content)
173
- return {
174
- file: f.file,
175
- type: meta.type || 'reference',
176
- description: meta.description || '',
177
- size: f.content.length,
178
- }
179
- })
180
- }
181
-
182
- /** 读单个记忆文件全文。 */
183
- async readFile(name) {
184
- const safe = safeName(name)
185
- if (!safe) throw new Error('sage-mem: invalid file name')
186
- const content = await readFile(join(MEMORY_DIR, safe), 'utf8')
187
- return { name: safe, content }
188
- }
189
-
190
- /** 写(新增或覆盖)单个记忆文件。 */
191
- async writeFile(name, content) {
192
- const safe = safeName(name)
193
- if (!safe) throw new Error('sage-mem: invalid file name')
194
- await writeFile(join(MEMORY_DIR, safe), String(content ?? ''), 'utf8')
195
- return { ok: true, file: safe }
196
- }
197
-
198
- /** 删除单个记忆文件。 */
199
- async deleteFile(name) {
200
- const safe = safeName(name)
201
- if (!safe) throw new Error('sage-mem: invalid file name')
202
- await unlink(join(MEMORY_DIR, safe))
203
- return { ok: true }
204
- }
205
- }
206
-
207
- markRemote(MemoryGateway, 'listFiles', 'listFiles')
208
- markRemote(MemoryGateway, 'readFile', 'readFile')
209
- markRemote(MemoryGateway, 'writeFile', 'writeFile')
210
- markRemote(MemoryGateway, 'deleteFile', 'deleteFile')
211
-
212
- export default MemoryGateway
1
+ /**
2
+ * sage-mem — 文件式记忆插件(CC 原生方式)。
3
+ *
4
+ * 记忆存在本地 markdown 文件目录(frontmatter + 正文,4 类),不是数据库。
5
+ * host 侧两件事:
6
+ * 1. 按问题检索注入:system-prompt/assemble 时扫 memory 目录,按双字匹配
7
+ * 选相关文件,读全文注入 system prompt,让 agent 第一轮就「想起」
8
+ * 2. TypertRemoteService:给设置页「记忆管理」提供文件列表/读/写/删
9
+ *
10
+ * 写入靠 AGENTS.md 的「记忆使用」规则引导 agent 自己判断 + 用文件工具写
11
+ * memory 目录——透明、可检查、防膨胀。无 worker、无 SQLite、无端口。
12
+ *
13
+ * 旧 worker 版见 git 分支 `sqlite-worker`。
14
+ */
15
+
16
+ import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
17
+ import { homedir } from 'node:os'
18
+ import { readFile, readdir, writeFile, unlink } from 'node:fs/promises'
19
+ import { join, basename } from 'node:path'
20
+
21
+ const MEMORY_DIR = process.env.SAGE_MEM_DIR || join(homedir(), '.sage-mem', 'memory')
22
+ const MAX_RESULTS = 5
23
+ const MAX_CHARS_PER_FILE = 1500
24
+
25
+ /** 提取消息文本,兼容 content 为字符串或 text-block 数组两种形态。 */
26
+ function extractText(content) {
27
+ if (typeof content === 'string') return content.trim()
28
+ if (!Array.isArray(content)) return ''
29
+ return content
30
+ .filter(b => b && b.type === 'text' && typeof b.text === 'string')
31
+ .map(b => b.text)
32
+ .join('\n')
33
+ .trim()
34
+ }
35
+
36
+ /** 提取双字(2-gram)集合,中英文数字通用。 */
37
+ function bigrams(s) {
38
+ const clean = String(s).toLowerCase().replace(/[^\p{L}\p{N}]+/gu, '')
39
+ const set = new Set()
40
+ for (let i = 0; i < clean.length - 1; i++) set.add(clean.slice(i, i + 2))
41
+ return set
42
+ }
43
+
44
+ /** query 的双字在 text 中的覆盖率(0~1),作为相关性得分。 */
45
+ function score(query, text) {
46
+ const q = bigrams(query)
47
+ if (q.size === 0) return 0
48
+ const t = bigrams(text)
49
+ let hit = 0
50
+ for (const g of q) if (t.has(g)) hit++
51
+ return hit / q.size
52
+ }
53
+
54
+ /** 扫 memory 目录,读回全部记忆文件的正文(跳过索引与子目录)。 */
55
+ async function scanMemoryFiles() {
56
+ try {
57
+ const entries = await readdir(MEMORY_DIR, { withFileTypes: true })
58
+ const names = entries
59
+ .filter(e => e.isFile() && e.name.endsWith('.md') && e.name !== 'MEMORY.md' && e.name !== 'session-log.md')
60
+ .map(e => e.name)
61
+ const loaded = await Promise.all(names.map(async (name) => {
62
+ try {
63
+ const content = await readFile(join(MEMORY_DIR, name), 'utf8')
64
+ return { file: name, content }
65
+ } catch {
66
+ return null
67
+ }
68
+ }))
69
+ return loaded.filter(Boolean)
70
+ } catch {
71
+ return []
72
+ }
73
+ }
74
+
75
+ /** 按得分选相关文件,降序取 top N。 */
76
+ function selectRelevant(query, files) {
77
+ return files
78
+ .map(f => ({ ...f, score: score(query, f.content) }))
79
+ .filter(f => f.score > 0)
80
+ .sort((a, b) => b.score - a.score)
81
+ .slice(0, MAX_RESULTS)
82
+ }
83
+
84
+ /** 解析 frontmatter 的 name/description/type(兼容顶层与 metadata 嵌套两种 type)。 */
85
+ function parseFrontmatter(content) {
86
+ const m = content.match(/^---\n([\s\S]*?)\n---/)
87
+ if (!m) return { name: '', description: '', type: '' }
88
+ const body = m[1]
89
+ const grab = (key) => {
90
+ const hit = body.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))
91
+ if (!hit) return ''
92
+ return hit[1].replace(/^["']|["']$/g, '').trim()
93
+ }
94
+ let type = grab('type')
95
+ if (!type) {
96
+ const nested = body.match(/^metadata:\s*\n(?:\s+[^\n]+\n)*?\s+type:\s*(\S+)/m)
97
+ if (nested) type = nested[1]
98
+ }
99
+ return { name: grab('name'), description: grab('description'), type }
100
+ }
101
+
102
+ /** 防路径穿越:只取 basename,且必须在 memory 目录内(.md,排除索引)。 */
103
+ function safeName(raw) {
104
+ const name = basename(String(raw || ''))
105
+ if (!name || !name.endsWith('.md') || name === 'MEMORY.md') return null
106
+ return name
107
+ }
108
+
109
+ /**
110
+ * 手动 @Remote marker 注册(Node 24 不解析装饰器语法,手写 decorator context)。
111
+ */
112
+ function markRemote(cls, method, exportName) {
113
+ const instance = Object.create(cls.prototype)
114
+ Remote(exportName)(undefined, {
115
+ kind: 'method',
116
+ name: method,
117
+ private: false,
118
+ static: false,
119
+ addInitializer(fn) {
120
+ fn.call(instance)
121
+ },
122
+ })
123
+ }
124
+
125
+ export class MemoryGateway extends TypertRemoteService {
126
+ constructor(ctx) {
127
+ super(ctx, 'memory')
128
+
129
+ // 按问题检索注入:system-prompt/assemble 是异步 waterfall,监听器可 await。
130
+ // 从 agent.session 拿当前 user message,扫 memory 目录按双字匹配选相关
131
+ // 文件,读全文作为动态 context 段注入——让 agent 第一轮就「想起」。
132
+ ctx.on('system-prompt/assemble', async (assembly, context, next) => {
133
+ const assembled = await next()
134
+ try {
135
+ const agent = context?.agent
136
+ const session = agent?.session
137
+ if (!session || typeof session.deriveMessages !== 'function') return assembled
138
+ const messages = [...session.deriveMessages()]
139
+ const lastUser = messages.reverse().find(m => m?.role === 'user')
140
+ const text = extractText(lastUser?.content)
141
+ if (!text || text.length < 2) return assembled
142
+
143
+ const files = await scanMemoryFiles()
144
+ const relevant = selectRelevant(text, files)
145
+ if (relevant.length === 0) return assembled
146
+
147
+ const parts = relevant.map(f => {
148
+ const body = f.content.length > MAX_CHARS_PER_FILE
149
+ ? f.content.slice(0, MAX_CHARS_PER_FILE) + '\n…(截断)'
150
+ : f.content
151
+ return `### ${f.file}\n${body}`
152
+ })
153
+
154
+ return {
155
+ ...assembled,
156
+ contexts: [...(assembled.contexts ?? []), {
157
+ name: 'sage-mem:recall',
158
+ order: 1000,
159
+ text: `以下是与你当前问题相关的历史记忆(文件式,来自 ${MEMORY_DIR}):\n\n${parts.join('\n\n')}`,
160
+ }],
161
+ }
162
+ } catch {
163
+ return assembled
164
+ }
165
+ })
166
+ }
167
+
168
+ /** 列出 memory 目录全部记忆文件(文件名 + 类型 + 描述 + 大小)。 */
169
+ async listFiles() {
170
+ const files = await scanMemoryFiles()
171
+ return files.map(f => {
172
+ const meta = parseFrontmatter(f.content)
173
+ return {
174
+ file: f.file,
175
+ type: meta.type || 'reference',
176
+ description: meta.description || '',
177
+ size: f.content.length,
178
+ }
179
+ })
180
+ }
181
+
182
+ /** 读单个记忆文件全文。 */
183
+ async readFile(name) {
184
+ const safe = safeName(name)
185
+ if (!safe) throw new Error('sage-mem: invalid file name')
186
+ const content = await readFile(join(MEMORY_DIR, safe), 'utf8')
187
+ return { name: safe, content }
188
+ }
189
+
190
+ /** 写(新增或覆盖)单个记忆文件。 */
191
+ async writeFile(name, content) {
192
+ const safe = safeName(name)
193
+ if (!safe) throw new Error('sage-mem: invalid file name')
194
+ await writeFile(join(MEMORY_DIR, safe), String(content ?? ''), 'utf8')
195
+ return { ok: true, file: safe }
196
+ }
197
+
198
+ /** 删除单个记忆文件。 */
199
+ async deleteFile(name) {
200
+ const safe = safeName(name)
201
+ if (!safe) throw new Error('sage-mem: invalid file name')
202
+ await unlink(join(MEMORY_DIR, safe))
203
+ return { ok: true }
204
+ }
205
+ }
206
+
207
+ markRemote(MemoryGateway, 'listFiles', 'listFiles')
208
+ markRemote(MemoryGateway, 'readFile', 'readFile')
209
+ markRemote(MemoryGateway, 'writeFile', 'writeFile')
210
+ markRemote(MemoryGateway, 'deleteFile', 'deleteFile')
211
+
212
+ export default MemoryGateway
@@ -1,90 +1,90 @@
1
- /**
2
- * sage-mem — 手写 strict typert host manifest(对齐官方生成文件格式)。
3
- * typert-loader 在插件挂载时读 package.json 的 exports["./typert"],
4
- * import 本文件并把 TYPERT 注册进 ctx.typert.local(strict 定义)。
5
- * schema 必须是 zod v4 实例(typert-loader 校验 "_zod" in schema)。
6
- */
7
- import { z } from 'zod'
8
-
9
- const fileNameSchema = z.string()
10
- const fileContentSchema = z.string()
11
-
12
- const fileSchema = z.object({
13
- 'file': z.string().readonly(),
14
- 'type': z.string().readonly(),
15
- 'description': z.string().readonly(),
16
- 'size': z.number().readonly(),
17
- }).readonly()
18
-
19
- const listResultSchema = z.array(fileSchema)
20
- const readResultSchema = z.object({
21
- 'name': z.string().readonly(),
22
- 'content': z.string().readonly(),
23
- }).readonly()
24
- const writeResultSchema = z.object({
25
- 'ok': z.boolean().readonly(),
26
- 'file': z.string().readonly(),
27
- }).readonly()
28
- const deleteResultSchema = z.object({
29
- 'ok': z.boolean().readonly(),
30
- }).readonly()
31
-
32
- export const TYPERT = {
33
- package: 'sage-mem',
34
- face: 'host',
35
- schemas: [],
36
- invocations: [
37
- {
38
- id: 'sage-mem#memory/listFiles',
39
- service: 'memory',
40
- namespace: 'memory',
41
- method: 'listFiles',
42
- invocation: { kind: 'direct' },
43
- parameters: [],
44
- result: { mode: 'strict', typeSymbol: 'sage-mem/types#FileList', schema: listResultSchema },
45
- sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
46
- },
47
- {
48
- id: 'sage-mem#memory/readFile',
49
- service: 'memory',
50
- namespace: 'memory',
51
- method: 'readFile',
52
- invocation: { kind: 'direct' },
53
- parameters: [
54
- { name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
55
- ],
56
- result: { mode: 'strict', typeSymbol: 'sage-mem/types#FileContent', schema: readResultSchema },
57
- sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
58
- },
59
- {
60
- id: 'sage-mem#memory/writeFile',
61
- service: 'memory',
62
- namespace: 'memory',
63
- method: 'writeFile',
64
- invocation: { kind: 'direct' },
65
- parameters: [
66
- { name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
67
- { name: 'content', wire: 'content', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileContent', schema: fileContentSchema } },
68
- ],
69
- result: { mode: 'strict', typeSymbol: 'sage-mem/types#WriteResult', schema: writeResultSchema },
70
- sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
71
- },
72
- {
73
- id: 'sage-mem#memory/deleteFile',
74
- service: 'memory',
75
- namespace: 'memory',
76
- method: 'deleteFile',
77
- invocation: { kind: 'direct' },
78
- parameters: [
79
- { name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
80
- ],
81
- result: { mode: 'strict', typeSymbol: 'sage-mem/types#DeleteResult', schema: deleteResultSchema },
82
- sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
83
- },
84
- ],
85
- model: {
86
- "services": [],
87
- "events": [],
88
- "objects": [],
89
- },
90
- }
1
+ /**
2
+ * sage-mem — 手写 strict typert host manifest(对齐官方生成文件格式)。
3
+ * typert-loader 在插件挂载时读 package.json 的 exports["./typert"],
4
+ * import 本文件并把 TYPERT 注册进 ctx.typert.local(strict 定义)。
5
+ * schema 必须是 zod v4 实例(typert-loader 校验 "_zod" in schema)。
6
+ */
7
+ import { z } from 'zod'
8
+
9
+ const fileNameSchema = z.string()
10
+ const fileContentSchema = z.string()
11
+
12
+ const fileSchema = z.object({
13
+ 'file': z.string().readonly(),
14
+ 'type': z.string().readonly(),
15
+ 'description': z.string().readonly(),
16
+ 'size': z.number().readonly(),
17
+ }).readonly()
18
+
19
+ const listResultSchema = z.array(fileSchema)
20
+ const readResultSchema = z.object({
21
+ 'name': z.string().readonly(),
22
+ 'content': z.string().readonly(),
23
+ }).readonly()
24
+ const writeResultSchema = z.object({
25
+ 'ok': z.boolean().readonly(),
26
+ 'file': z.string().readonly(),
27
+ }).readonly()
28
+ const deleteResultSchema = z.object({
29
+ 'ok': z.boolean().readonly(),
30
+ }).readonly()
31
+
32
+ export const TYPERT = {
33
+ package: 'sage-mem',
34
+ face: 'host',
35
+ schemas: [],
36
+ invocations: [
37
+ {
38
+ id: 'sage-mem#memory/listFiles',
39
+ service: 'memory',
40
+ namespace: 'memory',
41
+ method: 'listFiles',
42
+ invocation: { kind: 'direct' },
43
+ parameters: [],
44
+ result: { mode: 'strict', typeSymbol: 'sage-mem/types#FileList', schema: listResultSchema },
45
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
46
+ },
47
+ {
48
+ id: 'sage-mem#memory/readFile',
49
+ service: 'memory',
50
+ namespace: 'memory',
51
+ method: 'readFile',
52
+ invocation: { kind: 'direct' },
53
+ parameters: [
54
+ { name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
55
+ ],
56
+ result: { mode: 'strict', typeSymbol: 'sage-mem/types#FileContent', schema: readResultSchema },
57
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
58
+ },
59
+ {
60
+ id: 'sage-mem#memory/writeFile',
61
+ service: 'memory',
62
+ namespace: 'memory',
63
+ method: 'writeFile',
64
+ invocation: { kind: 'direct' },
65
+ parameters: [
66
+ { name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
67
+ { name: 'content', wire: 'content', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileContent', schema: fileContentSchema } },
68
+ ],
69
+ result: { mode: 'strict', typeSymbol: 'sage-mem/types#WriteResult', schema: writeResultSchema },
70
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
71
+ },
72
+ {
73
+ id: 'sage-mem#memory/deleteFile',
74
+ service: 'memory',
75
+ namespace: 'memory',
76
+ method: 'deleteFile',
77
+ invocation: { kind: 'direct' },
78
+ parameters: [
79
+ { name: 'name', wire: 'name', source: 'json', codec: { mode: 'strict', typeSymbol: 'sage-mem/types#FileName', schema: fileNameSchema } },
80
+ ],
81
+ result: { mode: 'strict', typeSymbol: 'sage-mem/types#DeleteResult', schema: deleteResultSchema },
82
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
83
+ },
84
+ ],
85
+ model: {
86
+ "services": [],
87
+ "events": [],
88
+ "objects": [],
89
+ },
90
+ }