@matatbread/matbot-core 0.4.15 → 0.4.17

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/src/session.ts CHANGED
@@ -1,4 +1,8 @@
1
- import type { Session, Message, MessageContent, MessageRole } from './types.js';
1
+ import type { Session, Message } from './types.js';
2
+
3
+ // `createMessage` lives in plugin-api, where the hook registry can also reach it (core is downstream of
4
+ // that package). Re-exported here so it stays where every consumer already imports it from.
5
+ export { createMessage } from '@matatbread/matbot-plugin-api';
2
6
 
3
7
  export interface CreateSessionOpts {
4
8
  title?: string;
@@ -32,20 +36,3 @@ export function appendMessage(session: Session, message: Message): Session {
32
36
  };
33
37
  }
34
38
 
35
- export function createMessage(opts: {
36
- role: MessageRole;
37
- content: MessageContent[];
38
- traceId: string;
39
- providerName?: string;
40
- metadata?: Record<string, unknown>;
41
- }): Message {
42
- return {
43
- id: crypto.randomUUID(),
44
- role: opts.role,
45
- content: opts.content,
46
- createdAt: new Date().toISOString(),
47
- traceId: opts.traceId,
48
- ...(opts.providerName !== undefined ? { providerName: opts.providerName } : {}),
49
- ...(opts.metadata !== undefined ? { metadata: opts.metadata } : {}),
50
- };
51
- }
package/src/settings.ts CHANGED
@@ -70,6 +70,9 @@ export function settingsDefaultNamespaces(): readonly string[] {
70
70
  * consumer filters `kind === ItemChangeKind && namespace === SETTINGS_NAMESPACE && id === slug(mine)`. */
71
71
  export const SETTINGS_NAMESPACE = 'settings';
72
72
 
73
+ /** How many times a settings write re-reads and re-compares before giving up. See `update` below. */
74
+ const SETTINGS_CAS_ATTEMPTS = 8;
75
+
73
76
  /**
74
77
  * The bus settings writes announce on, installed by the host at boot alongside {@link installSettingsDefaults}.
75
78
  *
@@ -134,15 +137,28 @@ export function makePluginSettings(rawStore: Store<SettingsDoc>, namespace: stri
134
137
 
135
138
  // One CAS retry loop for both writers: read, apply `mutate`, write. A document that vanished between
136
139
  // read and write (or never existed) is a plain `set`; otherwise `cas` guards the version we read.
140
+ //
141
+ // Bounded, because the loop cannot tell a lost race from a `cas` that will never succeed: a backend
142
+ // whose compare always fails (a version it does not mint, a medium generation it cannot reconcile)
143
+ // turned this into a hot spin with no error and no exit. A settings namespace is one document with few
144
+ // writers, so real contention resolves in one or two passes; exhausting the budget is a fault to
145
+ // report, not a state to keep retrying.
137
146
  const update = async (mutate: (data: Record<string, unknown>) => Record<string, unknown> | undefined): Promise<void> => {
138
- for (;;) {
147
+ for (let attempt = 1; ; attempt++) {
139
148
  const doc = await getDoc();
140
149
  const data = mutate(doc?.data ?? {});
141
150
  if (data === undefined) return;
142
- const next: SettingsDoc = { id, version: Date.now().toString(), data };
151
+ const next: SettingsDoc = { id, version: crypto.randomUUID(), data };
143
152
  if (doc === null) { await store.set(id, next); return; }
144
153
  const r = await store.cas(id, doc.version, next);
145
154
  if (r.ok) return;
155
+ if (attempt >= SETTINGS_CAS_ATTEMPTS) {
156
+ throw new Error(
157
+ `Settings for "${namespace}" could not be written: ${SETTINGS_CAS_ATTEMPTS} compare-and-swap ` +
158
+ 'attempts all lost. Either something is writing this namespace continuously, or the storage ' +
159
+ 'backend is rejecting every compare.',
160
+ );
161
+ }
146
162
  }
147
163
  };
148
164
 
@@ -5,6 +5,10 @@ interface TaggedContributor {
5
5
  pluginName?: string;
6
6
  }
7
7
 
8
+ /** The system prompt as sent: the parts joined, or null when there are none. */
9
+ export const joinSystemContext = (parts: readonly SystemContextPart[]): string | null =>
10
+ parts.length > 0 ? parts.map(p => p.text).join('\n\n') : null;
11
+
8
12
  export class SystemContextRegistryImpl implements SystemContextRegistry {
9
13
  private readonly _contributors: TaggedContributor[] = [];
10
14
 
@@ -32,9 +36,8 @@ export class SystemContextRegistryImpl implements SystemContextRegistry {
32
36
  }
33
37
 
34
38
  // The joined form is derived from the attributed one, so the prompt the model receives and the
35
- // breakdown `about_matbot` reports cannot drift: there is one traversal and one filter.
39
+ // breakdown `about_matbot` reports cannot drift: there is one traversal, one filter and one join.
36
40
  async build(ctx: { session: Session; signal: AbortSignal }): Promise<string | null> {
37
- const parts = await this.parts(ctx);
38
- return parts.length > 0 ? parts.map(p => p.text).join('\n\n') : null;
41
+ return joinSystemContext(await this.parts(ctx));
39
42
  }
40
43
  }
package/src/usage.ts CHANGED
@@ -12,12 +12,15 @@ export function addUsage(acc: Usage | undefined, next: Usage): Usage {
12
12
  const a = acc ?? { inputTokens: 0, outputTokens: 0 };
13
13
  const add = (x: number | undefined, y: number | undefined): number | undefined =>
14
14
  x === undefined && y === undefined ? undefined : (x ?? 0) + (y ?? 0);
15
+ const cacheReadTokens = add(a.cacheReadTokens, next.cacheReadTokens);
16
+ const cacheCreationTokens = add(a.cacheCreationTokens, next.cacheCreationTokens);
17
+ const reported = addReported(a.reported, next.reported);
15
18
  return {
16
19
  inputTokens: a.inputTokens + next.inputTokens,
17
20
  outputTokens: a.outputTokens + next.outputTokens,
18
- ...(((c) => c !== undefined ? { cacheReadTokens: c } : {}))(add(a.cacheReadTokens, next.cacheReadTokens)),
19
- ...(((c) => c !== undefined ? { cacheCreationTokens: c } : {}))(add(a.cacheCreationTokens, next.cacheCreationTokens)),
20
- ...(((r) => r !== undefined ? { reported: r } : {}))(addReported(a.reported, next.reported)),
21
+ ...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),
22
+ ...(cacheCreationTokens !== undefined ? { cacheCreationTokens } : {}),
23
+ ...(reported !== undefined ? { reported } : {}),
21
24
  };
22
25
  }
23
26