@moxxy/sdk 0.14.1 → 0.14.3
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/mode/collect-stream.d.ts +68 -0
- package/dist/mode/collect-stream.d.ts.map +1 -0
- package/dist/mode/collect-stream.js +181 -0
- package/dist/mode/collect-stream.js.map +1 -0
- package/dist/mode/project-messages.d.ts +84 -0
- package/dist/mode/project-messages.d.ts.map +1 -0
- package/dist/mode/project-messages.js +392 -0
- package/dist/mode/project-messages.js.map +1 -0
- package/dist/mode/single-shot.d.ts +17 -0
- package/dist/mode/single-shot.d.ts.map +1 -0
- package/dist/mode/single-shot.js +53 -0
- package/dist/mode/single-shot.js.map +1 -0
- package/dist/mode/stable-hash.d.ts +7 -0
- package/dist/mode/stable-hash.d.ts.map +1 -0
- package/dist/mode/stable-hash.js +20 -0
- package/dist/mode/stable-hash.js.map +1 -0
- package/dist/mode/stuck-loop.d.ts +39 -0
- package/dist/mode/stuck-loop.d.ts.map +1 -0
- package/dist/mode/stuck-loop.js +51 -0
- package/dist/mode/stuck-loop.js.map +1 -0
- package/dist/mode-helpers.d.ts +15 -175
- package/dist/mode-helpers.d.ts.map +1 -1
- package/dist/mode-helpers.js +14 -666
- package/dist/mode-helpers.js.map +1 -1
- package/dist/provider-utils.d.ts.map +1 -1
- package/dist/provider-utils.js +24 -0
- package/dist/provider-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/elision-state.test.ts +46 -0
- package/src/mode/collect-stream.ts +247 -0
- package/src/mode/project-messages.test.ts +121 -0
- package/src/mode/project-messages.ts +461 -0
- package/src/mode/single-shot.ts +63 -0
- package/src/mode/stable-hash.ts +20 -0
- package/src/mode/stuck-loop.ts +89 -0
- package/src/mode-helpers.ts +32 -850
- package/src/mode.test.ts +20 -0
- package/src/provider-utils.ts +24 -0
- package/src/token-accounting.test.ts +90 -0
package/src/mode.test.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { migrateModeName } from './mode.js';
|
|
3
|
+
|
|
4
|
+
describe('migrateModeName', () => {
|
|
5
|
+
it('maps every legacy mode name to its current target', () => {
|
|
6
|
+
expect(migrateModeName('tool-use')).toBe('default');
|
|
7
|
+
expect(migrateModeName('deep-research')).toBe('research');
|
|
8
|
+
expect(migrateModeName('plan-execute')).toBe('default');
|
|
9
|
+
expect(migrateModeName('bmad')).toBe('default');
|
|
10
|
+
expect(migrateModeName('developer')).toBe('default');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('passes a current/unknown name through unchanged (identity)', () => {
|
|
14
|
+
expect(migrateModeName('default')).toBe('default');
|
|
15
|
+
expect(migrateModeName('goal')).toBe('goal');
|
|
16
|
+
expect(migrateModeName('research')).toBe('research');
|
|
17
|
+
expect(migrateModeName('some-future-mode')).toBe('some-future-mode');
|
|
18
|
+
expect(migrateModeName('')).toBe('');
|
|
19
|
+
});
|
|
20
|
+
});
|
package/src/provider-utils.ts
CHANGED
|
@@ -173,6 +173,30 @@ export function zodToJsonSchema(schema: unknown): unknown {
|
|
|
173
173
|
const items = zodToJsonSchema((def as unknown as { type: unknown }).type);
|
|
174
174
|
return { type: 'array', items };
|
|
175
175
|
}
|
|
176
|
+
if (typeName === 'ZodRecord') {
|
|
177
|
+
// `z.record(keyType, valueType)` — an open-ended object whose values share
|
|
178
|
+
// one schema. Carry the value schema through `additionalProperties` rather
|
|
179
|
+
// than discarding it to the permissive fallback below.
|
|
180
|
+
const valueType = (def as unknown as { valueType: unknown }).valueType;
|
|
181
|
+
return { type: 'object', additionalProperties: zodToJsonSchema(valueType) };
|
|
182
|
+
}
|
|
183
|
+
if (typeName === 'ZodTuple') {
|
|
184
|
+
// ZodTuple._def.items is the ordered element schemas; `rest` (if set) is the
|
|
185
|
+
// variadic tail. Emit a fixed-position `items` array + an `additionalItems`
|
|
186
|
+
// schema for the rest, mirroring JSON-schema's positional-tuple form.
|
|
187
|
+
const tupleDef = def as unknown as { items: ReadonlyArray<unknown>; rest?: unknown };
|
|
188
|
+
const items = tupleDef.items.map((i) => zodToJsonSchema(i));
|
|
189
|
+
return {
|
|
190
|
+
type: 'array',
|
|
191
|
+
items,
|
|
192
|
+
...(tupleDef.rest != null ? { additionalItems: zodToJsonSchema(tupleDef.rest) } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (typeName === 'ZodAny' || typeName === 'ZodUnknown') {
|
|
196
|
+
// Genuinely unconstrained — advertise an empty (permissive) schema instead
|
|
197
|
+
// of a false `object` shape that would reject non-object values.
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
176
200
|
// Truly unknown — fall back to a permissive "any object" but spell out
|
|
177
201
|
// `properties: {}` and `additionalProperties: true` so strict validators
|
|
178
202
|
// accept the schema instead of bailing on a missing `properties` field.
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
asEventId,
|
|
5
5
|
asSessionId,
|
|
6
6
|
asTurnId,
|
|
7
|
+
summarizeSessionTokensFromEvents,
|
|
7
8
|
summarizeTokensByModel,
|
|
8
9
|
type ModelUsageTotals,
|
|
9
10
|
type MoxxyEvent,
|
|
@@ -78,6 +79,95 @@ describe('summarizeTokensByModel', () => {
|
|
|
78
79
|
});
|
|
79
80
|
});
|
|
80
81
|
|
|
82
|
+
describe('summarizeSessionTokensFromEvents — cost fold', () => {
|
|
83
|
+
it('counts only responses that reported usage', () => {
|
|
84
|
+
const s = summarizeSessionTokensFromEvents([
|
|
85
|
+
resp(0, {}), // no token fields → not counted
|
|
86
|
+
resp(1, { inputTokens: 100, outputTokens: 10 }),
|
|
87
|
+
resp(2, { cacheReadTokens: 50 }),
|
|
88
|
+
]);
|
|
89
|
+
expect(s.calls).toBe(2);
|
|
90
|
+
expect(s.totalInput).toBe(100);
|
|
91
|
+
expect(s.totalCacheRead).toBe(50);
|
|
92
|
+
expect(s.totalOutput).toBe(10);
|
|
93
|
+
expect(s.totalPrompt).toBe(150);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('applies the read 0.1x / write 1.25x billing multipliers', () => {
|
|
97
|
+
const s = summarizeSessionTokensFromEvents([
|
|
98
|
+
resp(0, { inputTokens: 100, cacheReadTokens: 1000, cacheCreationTokens: 200 }),
|
|
99
|
+
]);
|
|
100
|
+
// totalPrompt = 100 + 1000 + 200 = 1300 (== uncachedInputEq)
|
|
101
|
+
expect(s.totalPrompt).toBe(1300);
|
|
102
|
+
expect(s.uncachedInputEq).toBe(1300);
|
|
103
|
+
// billedInputEq = 100 + 1000*0.1 + 200*1.25 = 100 + 100 + 250 = 450
|
|
104
|
+
expect(s.billedInputEq).toBeCloseTo(450, 6);
|
|
105
|
+
expect(s.cacheHitRate).toBeCloseTo(1000 / 1300, 6);
|
|
106
|
+
expect(s.savedRatio).toBeCloseTo(1 - 450 / 1300, 6);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('guards divide-by-zero when no prompt tokens were reported', () => {
|
|
110
|
+
const s = summarizeSessionTokensFromEvents([resp(0, { outputTokens: 5 })]);
|
|
111
|
+
expect(s.totalPrompt).toBe(0);
|
|
112
|
+
expect(s.cacheHitRate).toBe(0);
|
|
113
|
+
expect(s.savedRatio).toBe(0);
|
|
114
|
+
// No writes → cache considered effective (deliberately-off cache never alarms).
|
|
115
|
+
expect(s.cacheEffective).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('reports cacheEffective=true for a healthy cache (high read ratio)', () => {
|
|
119
|
+
const events = Array.from({ length: 6 }, (_, i) =>
|
|
120
|
+
resp(i, { inputTokens: 10, cacheReadTokens: 1000, cacheCreationTokens: 100 }),
|
|
121
|
+
);
|
|
122
|
+
const s = summarizeSessionTokensFromEvents(events);
|
|
123
|
+
expect(s.calls).toBe(6);
|
|
124
|
+
expect(s.cacheHitRate).toBeGreaterThan(0.05);
|
|
125
|
+
expect(s.savedRatio).toBeGreaterThan(0);
|
|
126
|
+
expect(s.cacheEffective).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('trips cacheEffective=false on broken cache: >=5 calls, writes>0, near-zero reads', () => {
|
|
130
|
+
// 5 calls each writing cache but never reading it → cacheHitRate 0.
|
|
131
|
+
const events = Array.from({ length: 5 }, (_, i) =>
|
|
132
|
+
resp(i, { inputTokens: 100, cacheCreationTokens: 500, cacheReadTokens: 0 }),
|
|
133
|
+
);
|
|
134
|
+
const s = summarizeSessionTokensFromEvents(events);
|
|
135
|
+
expect(s.calls).toBe(5);
|
|
136
|
+
expect(s.totalCacheCreation).toBeGreaterThan(0);
|
|
137
|
+
expect(s.cacheHitRate).toBe(0);
|
|
138
|
+
expect(s.cacheEffective).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('exempts the broken-cache trip below 5 calls (boundary at calls===4 vs 5)', () => {
|
|
142
|
+
const make = (n: number) =>
|
|
143
|
+
summarizeSessionTokensFromEvents(
|
|
144
|
+
Array.from({ length: n }, (_, i) =>
|
|
145
|
+
resp(i, { inputTokens: 100, cacheCreationTokens: 500, cacheReadTokens: 0 }),
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
expect(make(4).cacheEffective).toBe(true); // < 5 → exempt
|
|
149
|
+
expect(make(5).cacheEffective).toBe(false); // >= 5 → trips
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('exempts when there are no cache writes even with zero reads over many calls', () => {
|
|
153
|
+
const events = Array.from({ length: 8 }, (_, i) =>
|
|
154
|
+
resp(i, { inputTokens: 100, cacheCreationTokens: 0, cacheReadTokens: 0 }),
|
|
155
|
+
);
|
|
156
|
+
expect(summarizeSessionTokensFromEvents(events).cacheEffective).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('does not trip when cacheHitRate sits exactly at the 0.05 threshold', () => {
|
|
160
|
+
// hitRate === 0.05 is NOT < 0.05, so the cache stays effective.
|
|
161
|
+
// read/(input+read+write) = 125 / 2500 = 0.05.
|
|
162
|
+
const events = Array.from({ length: 5 }, (_, i) =>
|
|
163
|
+
resp(i, { inputTokens: 1875, cacheReadTokens: 125, cacheCreationTokens: 500 }),
|
|
164
|
+
);
|
|
165
|
+
const s = summarizeSessionTokensFromEvents(events);
|
|
166
|
+
expect(s.cacheHitRate).toBeCloseTo(0.05, 6);
|
|
167
|
+
expect(s.cacheEffective).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
81
171
|
describe('addModelTotals', () => {
|
|
82
172
|
it('adds field-wise', () => {
|
|
83
173
|
const a: ModelUsageTotals = { calls: 1, inputTokens: 10, outputTokens: 2, cacheReadTokens: 3, cacheCreationTokens: 4 };
|