sidekick-shared 0.27.6 → 0.28.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/CHANGELOG.md +28 -0
- package/README.md +58 -18
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -2
- package/dist/claudePaths.d.ts +11 -0
- package/dist/claudePaths.js +61 -0
- package/dist/claudeProfiles.d.ts +2 -9
- package/dist/claudeProfiles.js +10 -25
- package/dist/codexPaths.d.ts +22 -0
- package/dist/codexPaths.js +67 -0
- package/dist/codexProfiles.d.ts +1 -4
- package/dist/codexProfiles.js +5 -43
- package/dist/doctor.js +2 -2
- package/dist/esm/accountChangeSignal.js +15 -0
- package/dist/esm/accountChanges.js +152 -0
- package/dist/esm/accountHealth.js +288 -0
- package/dist/esm/accountLaunch.js +145 -0
- package/dist/esm/accountLease.js +60 -0
- package/dist/esm/accountManager.js +606 -0
- package/dist/esm/accountRegistry.js +214 -0
- package/dist/esm/accountStatus.js +51 -0
- package/dist/esm/accountSwitch.js +74 -0
- package/dist/esm/accountSync.js +156 -0
- package/dist/esm/accountSyncTypes.js +4 -0
- package/dist/esm/accounts.js +1291 -0
- package/dist/esm/aggregation/EventAggregator.js +2034 -0
- package/dist/esm/aggregation/FrequencyTracker.js +69 -0
- package/dist/esm/aggregation/HeatmapTracker.js +89 -0
- package/dist/esm/aggregation/PatternExtractor.js +168 -0
- package/dist/esm/aggregation/costProvenance.js +31 -0
- package/dist/esm/aggregation/snapshot.js +161 -0
- package/dist/esm/aggregation/types.js +5 -0
- package/dist/esm/analytics/codeImpact.js +18 -0
- package/dist/esm/analytics/compactionLedger.js +37 -0
- package/dist/esm/analytics/qualityScore.js +112 -0
- package/dist/esm/autoSwitch.js +185 -0
- package/dist/esm/browser.js +34 -0
- package/dist/esm/claudeCredentialDoctor.js +239 -0
- package/dist/esm/claudeCredentialLedger.js +91 -0
- package/dist/esm/claudeCredentials.js +111 -0
- package/dist/esm/claudeLiveGuard.js +43 -0
- package/dist/esm/claudePaths.js +22 -0
- package/dist/esm/claudeProfiles.js +142 -0
- package/dist/esm/claudeRefreshLock.js +106 -0
- package/dist/esm/codexAuth.js +101 -0
- package/dist/esm/codexCredentialDoctor.js +265 -0
- package/dist/esm/codexCredentialStore.js +164 -0
- package/dist/esm/codexKeepAlive.js +112 -0
- package/dist/esm/codexPaths.js +172 -0
- package/dist/esm/codexProfiles.js +1251 -0
- package/dist/esm/codexQuota.js +564 -0
- package/dist/esm/codexQuotaWatcher.js +363 -0
- package/dist/esm/context/composer.js +71 -0
- package/dist/esm/context/sessionContext.js +546 -0
- package/dist/esm/credentialIO.js +283 -0
- package/dist/esm/credentials.js +49 -0
- package/dist/esm/doctor.js +305 -0
- package/dist/esm/ensureDefaultAccounts.js +62 -0
- package/dist/esm/errorHistory.js +56 -0
- package/dist/esm/extractors/errorTaxonomy.js +43 -0
- package/dist/esm/extractors/gatherAssets.js +32 -0
- package/dist/esm/extractors/sessionAssets.js +176 -0
- package/dist/esm/extractors/sources/claudeAssets.js +158 -0
- package/dist/esm/extractors/sources/codexAssets.js +234 -0
- package/dist/esm/extractors/tokenUsage.js +41 -0
- package/dist/esm/extractors/toolCall.js +142 -0
- package/dist/esm/failingTools.js +65 -0
- package/dist/esm/fileHead.js +27 -0
- package/dist/esm/formatters/eventHighlighter.js +332 -0
- package/dist/esm/formatters/noiseClassifier.js +209 -0
- package/dist/esm/formatters/sessionDump.js +328 -0
- package/dist/esm/formatters/toolSummary.js +226 -0
- package/dist/esm/formatting.js +82 -0
- package/dist/esm/handoffUrl.js +23 -0
- package/dist/esm/historicalStore.js +305 -0
- package/dist/esm/humanPrompt.js +88 -0
- package/dist/esm/index.js +213 -0
- package/dist/esm/keepAlive.js +189 -0
- package/dist/esm/liveCredentialGuard.js +33 -0
- package/dist/esm/modelAliases.js +51 -0
- package/dist/esm/modelCatalog.js +139 -0
- package/dist/esm/modelContext.js +280 -0
- package/dist/esm/modelInfo.js +832 -0
- package/dist/esm/multiProviderQuotaService.js +450 -0
- package/dist/esm/node.js +46 -0
- package/dist/esm/observedContextWindows.js +165 -0
- package/dist/esm/observedSessionCollector.js +939 -0
- package/dist/esm/parsers/changelogParser.js +71 -0
- package/dist/esm/parsers/codexHistory.js +89 -0
- package/dist/esm/parsers/codexParser.js +906 -0
- package/dist/esm/parsers/debugLogParser.js +126 -0
- package/dist/esm/parsers/jsonl.js +66 -0
- package/dist/esm/parsers/mcpToolName.js +28 -0
- package/dist/esm/parsers/openCodeParser.js +625 -0
- package/dist/esm/parsers/planExtractor.js +335 -0
- package/dist/esm/parsers/sessionActivityDetector.js +246 -0
- package/dist/esm/parsers/sessionPathResolver.js +713 -0
- package/dist/esm/parsers/subagentScanner.js +360 -0
- package/dist/esm/parsers/subagentStatsCache.js +45 -0
- package/dist/esm/parsers/subagentTraceParser.js +325 -0
- package/dist/esm/paths.js +134 -0
- package/dist/esm/peakHours.js +194 -0
- package/dist/esm/phrases.js +1330 -0
- package/dist/esm/pricingCatalog.js +247 -0
- package/dist/esm/processDetection.js +200 -0
- package/dist/esm/projectMigration.js +59 -0
- package/dist/esm/promptHistory.js +1221 -0
- package/dist/esm/providerFailure.js +362 -0
- package/dist/esm/providerIds.js +6 -0
- package/dist/esm/providerQuota.js +1 -0
- package/dist/esm/providerServiceStatus.js +145 -0
- package/dist/esm/providerServiceStatusTypes.js +1 -0
- package/dist/esm/providerStatus.js +117 -0
- package/dist/esm/providers/claudeCode.js +575 -0
- package/dist/esm/providers/claudeListing.js +121 -0
- package/dist/esm/providers/codex.js +1034 -0
- package/dist/esm/providers/codexDatabase.js +297 -0
- package/dist/esm/providers/codexRollouts.js +215 -0
- package/dist/esm/providers/detect.js +185 -0
- package/dist/esm/providers/diagnostics.js +83 -0
- package/dist/esm/providers/directoryListingCache.js +206 -0
- package/dist/esm/providers/factory.js +54 -0
- package/dist/esm/providers/openCode.js +2008 -0
- package/dist/esm/providers/openCodeDatabase.js +419 -0
- package/dist/esm/providers/rolloutWalker.js +252 -0
- package/dist/esm/providers/types.js +12 -0
- package/dist/esm/quota.js +182 -0
- package/dist/esm/quotaHistory.js +364 -0
- package/dist/esm/quotaPoller.js +230 -0
- package/dist/esm/quotaPresentation.js +97 -0
- package/dist/esm/quotaResolver.js +231 -0
- package/dist/esm/quotaSnapshots.js +130 -0
- package/dist/esm/quotaThresholds.js +85 -0
- package/dist/esm/readers/decisions.js +24 -0
- package/dist/esm/readers/handoff.js +20 -0
- package/dist/esm/readers/helpers.js +27 -0
- package/dist/esm/readers/history.js +9 -0
- package/dist/esm/readers/notes.js +48 -0
- package/dist/esm/readers/plans.js +225 -0
- package/dist/esm/readers/tasks.js +19 -0
- package/dist/esm/report/htmlHelpers.js +280 -0
- package/dist/esm/report/htmlReportGenerator.js +704 -0
- package/dist/esm/report/index.js +7 -0
- package/dist/esm/report/logo.js +2 -0
- package/dist/esm/report/openBrowser.js +19 -0
- package/dist/esm/report/sessionReportInputs.js +33 -0
- package/dist/esm/report/transcriptParser.js +83 -0
- package/dist/esm/report/types.js +4 -0
- package/dist/esm/schemas/accountManager.js +204 -0
- package/dist/esm/schemas/accountStatus.js +25 -0
- package/dist/esm/schemas/assistantTurn.js +74 -0
- package/dist/esm/schemas/index.js +25 -0
- package/dist/esm/schemas/observedSessionV1.js +77 -0
- package/dist/esm/schemas/providerFailure.js +37 -0
- package/dist/esm/schemas/quota.js +132 -0
- package/dist/esm/schemas/quotaHistory.js +40 -0
- package/dist/esm/schemas/sessionEvent.js +216 -0
- package/dist/esm/schemas/stateFile.js +75 -0
- package/dist/esm/schemas/tokenEstimation.js +7 -0
- package/dist/esm/schemas/transcript.js +86 -0
- package/dist/esm/schemas/usageNormalization.js +38 -0
- package/dist/esm/search/advancedFilter.js +197 -0
- package/dist/esm/search/sessionSearch.js +66 -0
- package/dist/esm/sessionFingerprint.js +40 -0
- package/dist/esm/sessionMonitor.js +198 -0
- package/dist/esm/sessionPreviews.js +522 -0
- package/dist/esm/sessionPromptHistory.js +254 -0
- package/dist/esm/sessionPromptSignals.js +364 -0
- package/dist/esm/sessionStats.js +160 -0
- package/dist/esm/sessionTokenTotals.js +98 -0
- package/dist/esm/sessionTranscripts.js +49 -0
- package/dist/esm/stateFile.js +119 -0
- package/dist/esm/statusline/BurnRateCalculator.js +40 -0
- package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
- package/dist/esm/statusline/formatter.js +122 -0
- package/dist/esm/statusline/index.js +8 -0
- package/dist/esm/terminalSync.js +95 -0
- package/dist/esm/testing/credentialGuard.js +197 -0
- package/dist/esm/testing/index.js +12 -0
- package/dist/esm/testing/sessionLogFixtures.js +128 -0
- package/dist/esm/tokenEstimation.js +67 -0
- package/dist/esm/tokenSummary.js +113 -0
- package/dist/esm/toolCategory.js +82 -0
- package/dist/esm/transcript.js +257 -0
- package/dist/esm/turns/assistantTurn.js +373 -0
- package/dist/esm/types/codex.js +23 -0
- package/dist/esm/types/decisionLog.js +5 -0
- package/dist/esm/types/historicalData.js +47 -0
- package/dist/esm/types/knowledgeNote.js +15 -0
- package/dist/esm/types/observedSessionV1.js +257 -0
- package/dist/esm/types/opencode.js +12 -0
- package/dist/esm/types/plan.js +7 -0
- package/dist/esm/types/sessionEvent.js +10 -0
- package/dist/esm/types/taskPersistence.js +12 -0
- package/dist/esm/usage/billingBlocks.js +148 -0
- package/dist/esm/usage/claudeUsageDedupe.js +189 -0
- package/dist/esm/usage/importSessionHistory.js +84 -0
- package/dist/esm/usage/subagentUsage.js +18 -0
- package/dist/esm/usage/usageEvents.js +402 -0
- package/dist/esm/usage/usageReports.js +202 -0
- package/dist/esm/usageNormalization.js +95 -0
- package/dist/esm/watchers/eventBridge.js +219 -0
- package/dist/esm/watchers/factory.js +63 -0
- package/dist/esm/watchers/jsonlTail.js +289 -0
- package/dist/esm/watchers/jsonlWatcher.js +435 -0
- package/dist/esm/watchers/providerReaderWatcher.js +118 -0
- package/dist/esm/watchers/sqliteWatcher.js +285 -0
- package/dist/esm/watchers/types.js +4 -0
- package/dist/esm/workspaceRoots.js +56 -0
- package/dist/esm/writers/atomic.js +375 -0
- package/dist/esm/writers/decisions.js +51 -0
- package/dist/esm/writers/notes.js +46 -0
- package/dist/esm/writers/tasks.js +63 -0
- package/dist/esm/zaiQuota.js +323 -0
- package/dist/esm/zaiQuotaApi.js +271 -0
- package/dist/esm/zaiQuotaWatcher.js +265 -0
- package/dist/fileHead.d.ts +22 -0
- package/dist/fileHead.js +33 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +49 -15
- package/dist/node.d.ts +13 -1
- package/dist/node.js +43 -2
- package/dist/parsers/codexHistory.js +2 -2
- package/dist/parsers/codexParser.d.ts +12 -0
- package/dist/parsers/codexParser.js +34 -12
- package/dist/parsers/debugLogParser.js +2 -2
- package/dist/parsers/sessionPathResolver.js +5 -4
- package/dist/promptHistory.d.ts +32 -0
- package/dist/promptHistory.js +115 -59
- package/dist/providerFailure.d.ts +12 -6
- package/dist/providerFailure.js +45 -0
- package/dist/providers/claudeCode.js +7 -99
- package/dist/providers/claudeListing.d.ts +22 -0
- package/dist/providers/claudeListing.js +158 -0
- package/dist/providers/codex.d.ts +3 -1
- package/dist/providers/codex.js +26 -227
- package/dist/providers/codexRollouts.d.ts +44 -0
- package/dist/providers/codexRollouts.js +261 -0
- package/dist/providers/detect.js +4 -4
- package/dist/readers/plans.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +7 -2
- package/dist/schemas/providerFailure.d.ts +154 -0
- package/dist/schemas/providerFailure.js +40 -0
- package/dist/sessionPromptHistory.d.ts +12 -7
- package/dist/sessionPromptHistory.js +15 -3
- package/dist/sessionPromptSignals.d.ts +9 -0
- package/dist/sessionPromptSignals.js +13 -0
- package/dist/testing/credentialGuard.d.ts +23 -0
- package/dist/testing/credentialGuard.js +233 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +22 -0
- package/dist/testing/sessionLogFixtures.d.ts +28 -0
- package/dist/testing/sessionLogFixtures.js +169 -0
- package/dist/tokenEstimation.d.ts +2 -0
- package/dist/tokenEstimation.js +21 -11
- package/dist/toolCategory.d.ts +18 -0
- package/dist/toolCategory.js +86 -0
- package/dist/types/codex.d.ts +4 -0
- package/dist/usage/usageEvents.d.ts +16 -0
- package/dist/usage/usageEvents.js +53 -9
- package/dist/watchers/jsonlTail.d.ts +26 -1
- package/dist/watchers/jsonlTail.js +136 -28
- package/dist/watchers/jsonlWatcher.js +7 -0
- package/dist/watchers/providerReaderWatcher.js +1 -0
- package/dist/watchers/types.d.ts +6 -0
- package/dist/workspaceRoots.d.ts +23 -0
- package/dist/workspaceRoots.js +93 -0
- package/package.json +22 -2
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.SIGNAL_TEXT_CHARS = void 0;
|
|
14
14
|
exports.createSessionRecordExtractor = createSessionRecordExtractor;
|
|
15
|
+
const mcpToolName_1 = require("./parsers/mcpToolName");
|
|
16
|
+
const toolCategory_1 = require("./toolCategory");
|
|
15
17
|
/** Error text kept on a signal; tool output can be arbitrarily long. */
|
|
16
18
|
exports.SIGNAL_TEXT_CHARS = 1024;
|
|
17
19
|
const CLAUDE_REJECTED_PREFIX = "The user doesn't want to proceed with this tool use";
|
|
@@ -64,6 +66,13 @@ class RecordExtractor {
|
|
|
64
66
|
const signal = { kind, timestamp, afterOrdinal: ordinal };
|
|
65
67
|
if (detail.tool)
|
|
66
68
|
signal.tool = detail.tool;
|
|
69
|
+
if (detail.tool || detail.mcp) {
|
|
70
|
+
const mcpServer = detail.mcpServer || (detail.tool ? (0, mcpToolName_1.parseMcpToolName)(detail.tool)?.serverName : undefined);
|
|
71
|
+
signal.toolCategory =
|
|
72
|
+
detail.mcp || !detail.tool ? 'mcp' : (0, toolCategory_1.classifyTool)(detail.tool, { mcpServer });
|
|
73
|
+
if (mcpServer)
|
|
74
|
+
signal.mcpServer = mcpServer;
|
|
75
|
+
}
|
|
67
76
|
if (detail.fullText) {
|
|
68
77
|
signal.text = detail.fullText;
|
|
69
78
|
}
|
|
@@ -234,6 +243,8 @@ class CodexRecordExtractor extends RecordExtractor {
|
|
|
234
243
|
if (failure !== null) {
|
|
235
244
|
this.signal('toolError', timestamp, ordinal, {
|
|
236
245
|
tool: invocation ? stringValue(invocation.tool) : undefined,
|
|
246
|
+
mcp: true,
|
|
247
|
+
mcpServer: invocation ? stringValue(invocation.server) : undefined,
|
|
237
248
|
text: failure,
|
|
238
249
|
});
|
|
239
250
|
}
|
|
@@ -273,6 +284,8 @@ class CodexRecordExtractor extends RecordExtractor {
|
|
|
273
284
|
if (failed) {
|
|
274
285
|
this.signal('toolError', timestamp, ordinal, {
|
|
275
286
|
tool: stringValue(row.tool),
|
|
287
|
+
mcp: true,
|
|
288
|
+
mcpServer: stringValue(row.server),
|
|
276
289
|
text: row.error !== undefined && row.error !== null
|
|
277
290
|
? errorText(row.error)
|
|
278
291
|
: (mcpFailure(row.result) ?? undefined),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CredentialGuard {
|
|
2
|
+
/** The developer's real home, from the passwd entry. */
|
|
3
|
+
readonly realHome: string;
|
|
4
|
+
/** The temp directory `HOME` now points at. */
|
|
5
|
+
readonly testHome: string;
|
|
6
|
+
/** Violations recorded since the last `take()`. */
|
|
7
|
+
readonly violations: string[];
|
|
8
|
+
/** Drain and return the recorded violations. */
|
|
9
|
+
take(): string[];
|
|
10
|
+
/** Throw when anything reached real credentials or homes since the last check. */
|
|
11
|
+
assertNoViolations(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface CredentialGuardOptions {
|
|
14
|
+
/** Binaries no child process may run (default `security`, `claude`, `codex`). */
|
|
15
|
+
blockedBinaries?: string[];
|
|
16
|
+
/** Extra real paths to protect, besides the Claude, Codex, and Sidekick homes. */
|
|
17
|
+
protectedPaths?: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Installs the guard once per process and returns it; later calls return the
|
|
21
|
+
* same guard and ignore their options.
|
|
22
|
+
*/
|
|
23
|
+
export declare function installCredentialGuard(options?: CredentialGuardOptions): CredentialGuard;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.installCredentialGuard = installCredentialGuard;
|
|
37
|
+
/**
|
|
38
|
+
* Credential guard for test runners, published as `sidekick-shared/testing`.
|
|
39
|
+
*
|
|
40
|
+
* No test may reach the real macOS `security` binary, the real `claude` /
|
|
41
|
+
* `codex` CLIs, or the developer's real Claude, Codex, or Sidekick homes: a
|
|
42
|
+
* stray keychain write copies a live refresh token, and Claude Code rotates
|
|
43
|
+
* that token on every refresh, so a second copy is a login waiting to die.
|
|
44
|
+
*
|
|
45
|
+
* Three layers:
|
|
46
|
+
* - `HOME` (and the Windows equivalents) point at a per-process temp dir,
|
|
47
|
+
* removed on exit, and the home overrides (`CLAUDE_CONFIG_DIR`,
|
|
48
|
+
* `CLAUDE_SECURESTORAGE_CONFIG_DIR`, `CODEX_HOME`, `SIDEKICK_CONFIG_DIR`)
|
|
49
|
+
* are unset, so every `os.homedir()`-derived path is isolated structurally;
|
|
50
|
+
* - the real `child_process` module object is patched, which reaches both ESM
|
|
51
|
+
* named imports (after `syncBuiltinESMExports`) and CommonJS code loaded
|
|
52
|
+
* through native `require`; per-file `vi.mock('child_process')` factories
|
|
53
|
+
* replace the module and never get here;
|
|
54
|
+
* - the real `fs` module refuses writes under the real homes and records reads.
|
|
55
|
+
*
|
|
56
|
+
* Production code swallows most errors, so violations are also recorded;
|
|
57
|
+
* call `assertNoViolations()` after each test (see `credentialGuard.setup.ts`
|
|
58
|
+
* for a vitest wiring). Framework-free; the real modules come from
|
|
59
|
+
* `process.getBuiltinModule` (Node 20.16+), or `require` in CommonJS.
|
|
60
|
+
*
|
|
61
|
+
* @module testing/credentialGuard
|
|
62
|
+
*/
|
|
63
|
+
const os = __importStar(require("node:os"));
|
|
64
|
+
const path = __importStar(require("node:path"));
|
|
65
|
+
const GUARD_KEY = '__sidekickCredentialGuard';
|
|
66
|
+
const DEFAULT_BLOCKED_BINARIES = ['security', 'claude', 'codex'];
|
|
67
|
+
const CHILD_PROCESS_FUNCTIONS = [
|
|
68
|
+
'spawn',
|
|
69
|
+
'spawnSync',
|
|
70
|
+
'execFile',
|
|
71
|
+
'execFileSync',
|
|
72
|
+
'exec',
|
|
73
|
+
'execSync',
|
|
74
|
+
];
|
|
75
|
+
const FS_WRITE_FUNCTIONS = [
|
|
76
|
+
'writeFileSync',
|
|
77
|
+
'appendFileSync',
|
|
78
|
+
'mkdirSync',
|
|
79
|
+
'rmSync',
|
|
80
|
+
'rmdirSync',
|
|
81
|
+
'unlinkSync',
|
|
82
|
+
'renameSync',
|
|
83
|
+
'copyFileSync',
|
|
84
|
+
'cpSync',
|
|
85
|
+
'chmodSync',
|
|
86
|
+
'symlinkSync',
|
|
87
|
+
];
|
|
88
|
+
const FS_READ_FUNCTIONS = [
|
|
89
|
+
'readFileSync',
|
|
90
|
+
'existsSync',
|
|
91
|
+
'statSync',
|
|
92
|
+
'lstatSync',
|
|
93
|
+
'readdirSync',
|
|
94
|
+
'openSync',
|
|
95
|
+
'watch',
|
|
96
|
+
];
|
|
97
|
+
/**
|
|
98
|
+
* Installs the guard once per process and returns it; later calls return the
|
|
99
|
+
* same guard and ignore their options.
|
|
100
|
+
*/
|
|
101
|
+
function installCredentialGuard(options = {}) {
|
|
102
|
+
const existing = globalThis[GUARD_KEY];
|
|
103
|
+
if (existing)
|
|
104
|
+
return existing;
|
|
105
|
+
const childProcess = builtin('node:child_process');
|
|
106
|
+
const realFs = builtin('node:fs');
|
|
107
|
+
const fs = realFs;
|
|
108
|
+
const { syncBuiltinESMExports } = builtin('node:module');
|
|
109
|
+
const blockedBinaries = new Set((options.blockedBinaries ?? DEFAULT_BLOCKED_BINARIES).map((name) => name.toLowerCase()));
|
|
110
|
+
// os.userInfo() reads the passwd entry, so it still names the real home
|
|
111
|
+
// after HOME is redirected below.
|
|
112
|
+
const realHome = os.userInfo().homedir;
|
|
113
|
+
const testHome = realFs.mkdtempSync(path.join(os.tmpdir(), 'sidekick-test-home-'));
|
|
114
|
+
const removeTestHome = realFs.rmSync.bind(realFs);
|
|
115
|
+
process.once('exit', () => {
|
|
116
|
+
try {
|
|
117
|
+
removeTestHome(testHome, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
/* best effort */
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const state = {
|
|
124
|
+
realHome,
|
|
125
|
+
testHome,
|
|
126
|
+
violations: [],
|
|
127
|
+
take() {
|
|
128
|
+
return state.violations.splice(0, state.violations.length);
|
|
129
|
+
},
|
|
130
|
+
assertNoViolations() {
|
|
131
|
+
const violations = state.take();
|
|
132
|
+
if (violations.length > 0) {
|
|
133
|
+
throw new Error(`Credential guard: this test reached real credentials or homes:\n- ${violations.join('\n- ')}`);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
process.env.HOME = testHome;
|
|
138
|
+
process.env.USERPROFILE = testHome;
|
|
139
|
+
process.env.APPDATA = path.join(testHome, 'AppData', 'Roaming');
|
|
140
|
+
process.env.LOCALAPPDATA = path.join(testHome, 'AppData', 'Local');
|
|
141
|
+
delete process.env.CLAUDE_CONFIG_DIR;
|
|
142
|
+
delete process.env.CLAUDE_SECURESTORAGE_CONFIG_DIR;
|
|
143
|
+
delete process.env.CODEX_HOME;
|
|
144
|
+
delete process.env.SIDEKICK_CONFIG_DIR;
|
|
145
|
+
const protectedRoots = path.resolve(realHome) === path.resolve(testHome)
|
|
146
|
+
? []
|
|
147
|
+
: [
|
|
148
|
+
path.join(realHome, '.claude'),
|
|
149
|
+
path.join(realHome, '.claude.json'),
|
|
150
|
+
path.join(realHome, '.codex'),
|
|
151
|
+
path.join(realHome, '.config', 'sidekick'),
|
|
152
|
+
...(options.protectedPaths ?? []),
|
|
153
|
+
];
|
|
154
|
+
const isProtected = (target) => {
|
|
155
|
+
let candidate = null;
|
|
156
|
+
if (typeof target === 'string')
|
|
157
|
+
candidate = target;
|
|
158
|
+
else if (target instanceof URL && target.protocol === 'file:')
|
|
159
|
+
candidate = target.pathname;
|
|
160
|
+
if (!candidate)
|
|
161
|
+
return null;
|
|
162
|
+
const resolved = path.resolve(candidate);
|
|
163
|
+
return protectedRoots.some((root) => resolved === root || resolved.startsWith(root + path.sep))
|
|
164
|
+
? resolved
|
|
165
|
+
: null;
|
|
166
|
+
};
|
|
167
|
+
const binaryName = (command) => {
|
|
168
|
+
if (typeof command !== 'string')
|
|
169
|
+
return '';
|
|
170
|
+
const first = command.trim().split(/\s+/)[0] ?? '';
|
|
171
|
+
return path
|
|
172
|
+
.basename(first.replace(/^["']|["']$/g, ''))
|
|
173
|
+
.replace(/\.(exe|cmd|bat)$/i, '')
|
|
174
|
+
.toLowerCase();
|
|
175
|
+
};
|
|
176
|
+
const record = (message) => {
|
|
177
|
+
state.violations.push(message);
|
|
178
|
+
throw new Error(`[credential guard] ${message}`);
|
|
179
|
+
};
|
|
180
|
+
for (const name of CHILD_PROCESS_FUNCTIONS) {
|
|
181
|
+
const original = childProcess[name];
|
|
182
|
+
if (typeof original !== 'function')
|
|
183
|
+
continue;
|
|
184
|
+
childProcess[name] = function guarded(...args) {
|
|
185
|
+
const binary = binaryName(args[0]);
|
|
186
|
+
if (blockedBinaries.has(binary)) {
|
|
187
|
+
record(`child_process.${name}('${binary}') reached the real binary; mock child_process or inject a runner`);
|
|
188
|
+
}
|
|
189
|
+
return original.apply(this, args);
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
for (const name of FS_WRITE_FUNCTIONS) {
|
|
193
|
+
const original = fs[name];
|
|
194
|
+
if (typeof original !== 'function')
|
|
195
|
+
continue;
|
|
196
|
+
fs[name] = function guarded(...args) {
|
|
197
|
+
for (const arg of args.slice(0, 2)) {
|
|
198
|
+
const hit = isProtected(arg);
|
|
199
|
+
if (hit)
|
|
200
|
+
record(`fs.${name}(${hit}) would write inside the real home`);
|
|
201
|
+
}
|
|
202
|
+
return original.apply(this, args);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
for (const name of FS_READ_FUNCTIONS) {
|
|
206
|
+
const original = fs[name];
|
|
207
|
+
if (typeof original !== 'function')
|
|
208
|
+
continue;
|
|
209
|
+
fs[name] = function guarded(...args) {
|
|
210
|
+
const hit = isProtected(args[0]);
|
|
211
|
+
// Reads are recorded rather than thrown so existsSync keeps its
|
|
212
|
+
// never-throws contract; the afterEach below still fails the test.
|
|
213
|
+
if (hit)
|
|
214
|
+
state.violations.push(`fs.${name}(${hit}) read the real home`);
|
|
215
|
+
return original.apply(this, args);
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
syncBuiltinESMExports();
|
|
219
|
+
globalThis[GUARD_KEY] = state;
|
|
220
|
+
return state;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The real module object of a Node built-in: `process.getBuiltinModule`
|
|
224
|
+
* (Node 20.16+), or `require` in CommonJS on older Node.
|
|
225
|
+
*/
|
|
226
|
+
function builtin(id) {
|
|
227
|
+
if (typeof process.getBuiltinModule === 'function')
|
|
228
|
+
return process.getBuiltinModule(id);
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- CommonJS fallback for Node < 20.16
|
|
230
|
+
if (typeof require === 'function')
|
|
231
|
+
return require(id);
|
|
232
|
+
throw new Error('installCredentialGuard() needs Node 20.16+ outside CommonJS.');
|
|
233
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing entry (`sidekick-shared/testing`): helpers for test suites that
|
|
3
|
+
* exercise sidekick-shared against Claude Code and Codex session data.
|
|
4
|
+
*
|
|
5
|
+
* - `installCredentialGuard()` keeps tests away from the real `security`,
|
|
6
|
+
* `claude`, and `codex` binaries and the real Claude, Codex, and Sidekick
|
|
7
|
+
* homes.
|
|
8
|
+
* - The session-log builders write Claude Code and Codex logs in the shapes
|
|
9
|
+
* the readers parse.
|
|
10
|
+
*/
|
|
11
|
+
export { installCredentialGuard } from './credentialGuard';
|
|
12
|
+
export type { CredentialGuard, CredentialGuardOptions } from './credentialGuard';
|
|
13
|
+
export { appendJsonl, claudeFiller, codexFiller, createSessionLogFixtures, ts, writeJsonl, } from './sessionLogFixtures';
|
|
14
|
+
export type { Row, SessionLogFixtureContext } from './sessionLogFixtures';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeJsonl = exports.ts = exports.createSessionLogFixtures = exports.codexFiller = exports.claudeFiller = exports.appendJsonl = exports.installCredentialGuard = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Testing entry (`sidekick-shared/testing`): helpers for test suites that
|
|
6
|
+
* exercise sidekick-shared against Claude Code and Codex session data.
|
|
7
|
+
*
|
|
8
|
+
* - `installCredentialGuard()` keeps tests away from the real `security`,
|
|
9
|
+
* `claude`, and `codex` binaries and the real Claude, Codex, and Sidekick
|
|
10
|
+
* homes.
|
|
11
|
+
* - The session-log builders write Claude Code and Codex logs in the shapes
|
|
12
|
+
* the readers parse.
|
|
13
|
+
*/
|
|
14
|
+
var credentialGuard_1 = require("./credentialGuard");
|
|
15
|
+
Object.defineProperty(exports, "installCredentialGuard", { enumerable: true, get: function () { return credentialGuard_1.installCredentialGuard; } });
|
|
16
|
+
var sessionLogFixtures_1 = require("./sessionLogFixtures");
|
|
17
|
+
Object.defineProperty(exports, "appendJsonl", { enumerable: true, get: function () { return sessionLogFixtures_1.appendJsonl; } });
|
|
18
|
+
Object.defineProperty(exports, "claudeFiller", { enumerable: true, get: function () { return sessionLogFixtures_1.claudeFiller; } });
|
|
19
|
+
Object.defineProperty(exports, "codexFiller", { enumerable: true, get: function () { return sessionLogFixtures_1.codexFiller; } });
|
|
20
|
+
Object.defineProperty(exports, "createSessionLogFixtures", { enumerable: true, get: function () { return sessionLogFixtures_1.createSessionLogFixtures; } });
|
|
21
|
+
Object.defineProperty(exports, "ts", { enumerable: true, get: function () { return sessionLogFixtures_1.ts; } });
|
|
22
|
+
Object.defineProperty(exports, "writeJsonl", { enumerable: true, get: function () { return sessionLogFixtures_1.writeJsonl; } });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code and Codex session-log builders shared by the prompt-history
|
|
3
|
+
* tests, published through `sidekick-shared/testing`.
|
|
4
|
+
*/
|
|
5
|
+
export type Row = Record<string, unknown>;
|
|
6
|
+
export declare function ts(second: number): string;
|
|
7
|
+
export declare function writeJsonl(file: string, rows: Row[]): void;
|
|
8
|
+
export declare function appendJsonl(file: string, rows: Row[]): void;
|
|
9
|
+
/** A Claude Code line with no message: read and ignored, like progress records. */
|
|
10
|
+
export declare function claudeFiller(bytes: number, second: number): Row;
|
|
11
|
+
/** A Codex line that is neither a prompt nor usage. */
|
|
12
|
+
export declare function codexFiller(bytes: number, second: number): Row;
|
|
13
|
+
export interface SessionLogFixtureContext {
|
|
14
|
+
/** The mocked home directory holding `.claude/` and `.codex/`. */
|
|
15
|
+
home: string;
|
|
16
|
+
/** Default working directory recorded on Claude lines. */
|
|
17
|
+
repo: string;
|
|
18
|
+
}
|
|
19
|
+
/** Builders that read the per-test home and repo lazily from `context`. */
|
|
20
|
+
export declare function createSessionLogFixtures(context: () => SessionLogFixtureContext): {
|
|
21
|
+
claudeFile: (workspace: string, sessionId: string) => string;
|
|
22
|
+
claudeUser: (content: unknown, second: number, extra?: Row) => Row;
|
|
23
|
+
claudeAssistant: (id: string, second: number, extra?: Row, message?: Row) => Row;
|
|
24
|
+
codexFile: () => string;
|
|
25
|
+
codexMeta: (cwd: string | undefined, source?: unknown, extra?: Row) => Row;
|
|
26
|
+
codexUser: (texts: string[], second: number) => Row;
|
|
27
|
+
codexTokenCount: (second: number) => Row;
|
|
28
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code and Codex session-log builders shared by the prompt-history
|
|
4
|
+
* tests, published through `sidekick-shared/testing`.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ts = ts;
|
|
41
|
+
exports.writeJsonl = writeJsonl;
|
|
42
|
+
exports.appendJsonl = appendJsonl;
|
|
43
|
+
exports.claudeFiller = claudeFiller;
|
|
44
|
+
exports.codexFiller = codexFiller;
|
|
45
|
+
exports.createSessionLogFixtures = createSessionLogFixtures;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const sessionPathResolver_1 = require("../parsers/sessionPathResolver");
|
|
49
|
+
function ts(second) {
|
|
50
|
+
return new Date(Date.UTC(2026, 8, 1, 10, 0, second)).toISOString();
|
|
51
|
+
}
|
|
52
|
+
function writeJsonl(file, rows) {
|
|
53
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
54
|
+
fs.writeFileSync(file, rows.map((row) => JSON.stringify(row)).join('\n') + '\n');
|
|
55
|
+
}
|
|
56
|
+
function appendJsonl(file, rows) {
|
|
57
|
+
fs.appendFileSync(file, rows.map((row) => JSON.stringify(row)).join('\n') + '\n');
|
|
58
|
+
}
|
|
59
|
+
/** A Claude Code line with no message: read and ignored, like progress records. */
|
|
60
|
+
function claudeFiller(bytes, second) {
|
|
61
|
+
return { type: 'system', subtype: 'filler', timestamp: ts(second), content: 'f'.repeat(bytes) };
|
|
62
|
+
}
|
|
63
|
+
/** A Codex line that is neither a prompt nor usage. */
|
|
64
|
+
function codexFiller(bytes, second) {
|
|
65
|
+
return {
|
|
66
|
+
timestamp: ts(second),
|
|
67
|
+
type: 'event_msg',
|
|
68
|
+
payload: { type: 'agent_message', message: 'f'.repeat(bytes) },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** Builders that read the per-test home and repo lazily from `context`. */
|
|
72
|
+
function createSessionLogFixtures(context) {
|
|
73
|
+
let codexCounter = 0;
|
|
74
|
+
function claudeFile(workspace, sessionId) {
|
|
75
|
+
return path.join(context().home, '.claude', 'projects', (0, sessionPathResolver_1.encodeWorkspacePath)(workspace), `${sessionId}.jsonl`);
|
|
76
|
+
}
|
|
77
|
+
function claudeUser(content, second, extra = {}) {
|
|
78
|
+
return {
|
|
79
|
+
type: 'user',
|
|
80
|
+
entrypoint: 'cli',
|
|
81
|
+
cwd: context().repo,
|
|
82
|
+
gitBranch: 'main',
|
|
83
|
+
timestamp: ts(second),
|
|
84
|
+
message: { role: 'user', content },
|
|
85
|
+
...extra,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** One Claude Code assistant record; `message` fields are merged over the defaults. */
|
|
89
|
+
function claudeAssistant(id, second, extra = {}, message = {}) {
|
|
90
|
+
return {
|
|
91
|
+
type: 'assistant',
|
|
92
|
+
entrypoint: 'cli',
|
|
93
|
+
cwd: context().repo,
|
|
94
|
+
timestamp: ts(second),
|
|
95
|
+
message: {
|
|
96
|
+
id,
|
|
97
|
+
role: 'assistant',
|
|
98
|
+
model: 'claude-opus-5',
|
|
99
|
+
content: [{ type: 'text', text: 'ok' }],
|
|
100
|
+
stop_reason: 'end_turn',
|
|
101
|
+
usage: {
|
|
102
|
+
input_tokens: 10,
|
|
103
|
+
output_tokens: 20,
|
|
104
|
+
cache_read_input_tokens: 30,
|
|
105
|
+
cache_creation_input_tokens: 40,
|
|
106
|
+
},
|
|
107
|
+
...message,
|
|
108
|
+
},
|
|
109
|
+
...extra,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function codexFile() {
|
|
113
|
+
codexCounter++;
|
|
114
|
+
const id = `019d86b0-b20c-7b02-a3b2-${String(codexCounter).padStart(12, '0')}`;
|
|
115
|
+
return path.join(context().home, '.codex', 'sessions', '2026', '09', '01', `rollout-2026-09-01T10-00-${String(codexCounter).padStart(2, '0')}-${id}.jsonl`);
|
|
116
|
+
}
|
|
117
|
+
function codexMeta(cwd, source = 'cli', extra = {}) {
|
|
118
|
+
return {
|
|
119
|
+
timestamp: ts(0),
|
|
120
|
+
type: 'session_meta',
|
|
121
|
+
payload: {
|
|
122
|
+
id: 'meta',
|
|
123
|
+
timestamp: ts(0),
|
|
124
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
125
|
+
originator: 'codex-tui',
|
|
126
|
+
source,
|
|
127
|
+
git: { branch: 'feature/x' },
|
|
128
|
+
...extra,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function codexUser(texts, second) {
|
|
133
|
+
return {
|
|
134
|
+
timestamp: ts(second),
|
|
135
|
+
type: 'response_item',
|
|
136
|
+
payload: {
|
|
137
|
+
type: 'message',
|
|
138
|
+
role: 'user',
|
|
139
|
+
content: texts.map((text) => ({ type: 'input_text', text })),
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function codexTokenCount(second) {
|
|
144
|
+
return {
|
|
145
|
+
timestamp: ts(second),
|
|
146
|
+
type: 'event_msg',
|
|
147
|
+
payload: {
|
|
148
|
+
type: 'token_count',
|
|
149
|
+
info: {
|
|
150
|
+
last_token_usage: {
|
|
151
|
+
input_tokens: 100,
|
|
152
|
+
cached_input_tokens: 60,
|
|
153
|
+
output_tokens: 7,
|
|
154
|
+
cache_write_input_tokens: 5,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
claudeFile,
|
|
162
|
+
claudeUser,
|
|
163
|
+
claudeAssistant,
|
|
164
|
+
codexFile,
|
|
165
|
+
codexMeta,
|
|
166
|
+
codexUser,
|
|
167
|
+
codexTokenCount,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -13,6 +13,8 @@ export interface TokenEstimate {
|
|
|
13
13
|
confidence: TokenEstimationConfidence;
|
|
14
14
|
provenance: string;
|
|
15
15
|
}
|
|
16
|
+
/** Latin/source-code characters per token in the fallback estimate. */
|
|
17
|
+
export declare const ASCII_CHARS_PER_TOKEN = 3.5;
|
|
16
18
|
/**
|
|
17
19
|
* Estimate tokens without bundling a tokenizer.
|
|
18
20
|
*
|
package/dist/tokenEstimation.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ASCII_CHARS_PER_TOKEN = void 0;
|
|
3
4
|
exports.estimateTextTokens = estimateTextTokens;
|
|
4
5
|
exports.estimateSerializedTokens = estimateSerializedTokens;
|
|
6
|
+
/** Latin/source-code characters per token in the fallback estimate. */
|
|
7
|
+
exports.ASCII_CHARS_PER_TOKEN = 3.5;
|
|
5
8
|
const CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
6
9
|
const EMOJI = /\p{Extended_Pictographic}/u;
|
|
7
10
|
/**
|
|
@@ -30,19 +33,26 @@ function estimateTextTokens(text, options = {}) {
|
|
|
30
33
|
provenance: options.model ?? 'injected-counter',
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
|
-
|
|
36
|
+
// ASCII is never CJK or pictographic, so the leading ASCII run is counted
|
|
37
|
+
// without the per-code-point regex tests; most source text is all ASCII.
|
|
38
|
+
let firstNonAscii = 0;
|
|
39
|
+
while (firstNonAscii < text.length && text.charCodeAt(firstNonAscii) <= 127)
|
|
40
|
+
firstNonAscii++;
|
|
41
|
+
let asciiChars = firstNonAscii;
|
|
34
42
|
let conservativeTokens = 0;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
if (firstNonAscii < text.length) {
|
|
44
|
+
for (const codePoint of text.slice(firstNonAscii)) {
|
|
45
|
+
if (EMOJI.test(codePoint))
|
|
46
|
+
conservativeTokens += 2;
|
|
47
|
+
else if (CJK.test(codePoint))
|
|
48
|
+
conservativeTokens += 1;
|
|
49
|
+
else if ((codePoint.codePointAt(0) ?? 128) <= 127)
|
|
50
|
+
asciiChars += codePoint.length;
|
|
51
|
+
else
|
|
52
|
+
conservativeTokens += 1;
|
|
53
|
+
}
|
|
44
54
|
}
|
|
45
|
-
const count = Math.ceil(asciiChars /
|
|
55
|
+
const count = Math.ceil(asciiChars / exports.ASCII_CHARS_PER_TOKEN) + conservativeTokens;
|
|
46
56
|
return {
|
|
47
57
|
count: Number.isFinite(count) && count >= 0 ? count : 0,
|
|
48
58
|
method: 'sidekick-fallback-v1',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One vocabulary for what a tool call does, across Claude Code, Codex, and
|
|
3
|
+
* OpenCode tool names. Browser-safe: no Node imports.
|
|
4
|
+
*
|
|
5
|
+
* @module toolCategory
|
|
6
|
+
*/
|
|
7
|
+
export declare const TOOL_CATEGORIES: readonly ["shell", "file_read", "file_edit", "search", "web", "subagent", "planning", "mcp", "other"];
|
|
8
|
+
export type ToolCategory = (typeof TOOL_CATEGORIES)[number];
|
|
9
|
+
export interface ClassifyToolOptions {
|
|
10
|
+
/** The MCP server the call went to, when the log records it separately (Codex). */
|
|
11
|
+
mcpServer?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Classifies a tool name from any supported provider. `mcp__<server>__<tool>`
|
|
15
|
+
* names and calls with a known `mcpServer` are `mcp`; unknown names are
|
|
16
|
+
* `other`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function classifyTool(name: string, options?: ClassifyToolOptions): ToolCategory;
|