cortico 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { QuoteTime } from '../providers/pricebook.ts';
|
|
2
|
+
import type { ModelSpec } from './types.ts';
|
|
3
|
+
import type { Request, Response, StreamEvent, Usage } from '../protocol/open-responses/index.ts';
|
|
4
|
+
import type { ContextRecord, ItemOrigin } from '../protocol/open-responses/context.ts';
|
|
5
|
+
|
|
6
|
+
/** Missing meters remain null. Native details are retained for future reconciliation. */
|
|
7
|
+
export interface TokenMeters {
|
|
8
|
+
input: number | null;
|
|
9
|
+
output: number | null;
|
|
10
|
+
total: number | null;
|
|
11
|
+
cachedInput: number | null;
|
|
12
|
+
uncachedInput: number | null;
|
|
13
|
+
reasoning: number | null;
|
|
14
|
+
details?: Record<string, { quantity: number | null; unit: string }>;
|
|
15
|
+
native: Record<string, unknown> | null;
|
|
16
|
+
}
|
|
17
|
+
export type Meter = Exclude<keyof TokenMeters, 'native' | 'details'> | `detail:${string}`;
|
|
18
|
+
export interface PriceRule { meter: Meter; perMillion: number; unit?: string; }
|
|
19
|
+
export interface PriceSnapshot {
|
|
20
|
+
id: string;
|
|
21
|
+
currency: string;
|
|
22
|
+
basis: 'marginal' | 'equivalent';
|
|
23
|
+
rules: PriceRule[];
|
|
24
|
+
inputBands?: Array<{ from: number; rules: PriceRule[] }>;
|
|
25
|
+
serviceTiers?: Record<string, { rules: PriceRule[]; inputBands?: Array<{ from: number; rules: PriceRule[] }> }>;
|
|
26
|
+
source: string;
|
|
27
|
+
capturedAt: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Charge {
|
|
30
|
+
quote: PriceSnapshot;
|
|
31
|
+
amount: number | null;
|
|
32
|
+
knownAmount: number;
|
|
33
|
+
missing: Array<Meter | 'serviceTier'>;
|
|
34
|
+
lines: Array<{ meter: Meter; unit: string; quantity: number | null; perMillion: number; amount: number | null }>;
|
|
35
|
+
}
|
|
36
|
+
export interface ProviderAttempt {
|
|
37
|
+
id: string;
|
|
38
|
+
generationId: string;
|
|
39
|
+
ordinal: number;
|
|
40
|
+
origin: ItemOrigin;
|
|
41
|
+
startedAt: string;
|
|
42
|
+
elapsedMs: number;
|
|
43
|
+
requestId: string | null;
|
|
44
|
+
responseId: string | null;
|
|
45
|
+
outcome: 'completed' | 'incomplete' | 'failed' | 'aborted' | 'discarded';
|
|
46
|
+
status: number | null;
|
|
47
|
+
serviceTier: string | null;
|
|
48
|
+
requestedServiceTier?: string | null;
|
|
49
|
+
purpose?: 'generation' | 'diagnostic';
|
|
50
|
+
meters: TokenMeters;
|
|
51
|
+
charges: Charge[];
|
|
52
|
+
}
|
|
53
|
+
export interface GenerateOptions {
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
onEvent?: (event: StreamEvent) => void;
|
|
56
|
+
role?: string;
|
|
57
|
+
sessionId?: string;
|
|
58
|
+
/** Local context retains output reasoning and media references alongside the request Items. */
|
|
59
|
+
context?: readonly ContextRecord[];
|
|
60
|
+
nativeSpec?: ModelSpec;
|
|
61
|
+
origin?: ItemOrigin;
|
|
62
|
+
quote?: (at: QuoteTime) => readonly PriceSnapshot[];
|
|
63
|
+
/** Diagnostic requests use a single attempt and cannot recursively diagnose. */
|
|
64
|
+
diagnostic?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface Generation {
|
|
67
|
+
response: Response;
|
|
68
|
+
origin: ItemOrigin;
|
|
69
|
+
attempts: ProviderAttempt[];
|
|
70
|
+
}
|
|
71
|
+
export interface ResponseClient {
|
|
72
|
+
respond(request: Request, options?: GenerateOptions): Promise<Generation>;
|
|
73
|
+
/** A fork captures its provider binding once, before its first request. */
|
|
74
|
+
bind?(): ResponseClient;
|
|
75
|
+
}
|
|
76
|
+
export class GenerationError extends Error {
|
|
77
|
+
constructor(message: string, readonly attempts: ProviderAttempt[], readonly partial: Response | null,
|
|
78
|
+
readonly origin: ItemOrigin, readonly status = 0, readonly body = '', options?: ErrorOptions) {
|
|
79
|
+
super(message, options);
|
|
80
|
+
this.name = 'GenerationError';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function unknownMeters(): TokenMeters {
|
|
84
|
+
return { input: null, output: null, total: null, cachedInput: null, uncachedInput: null, reasoning: null, native: null };
|
|
85
|
+
}
|
|
86
|
+
export function standardUsage(meters: TokenMeters): Usage | null {
|
|
87
|
+
const { input, output, total, cachedInput, reasoning } = meters;
|
|
88
|
+
if (input === null || output === null || total === null || cachedInput === null || reasoning === null) return null;
|
|
89
|
+
return { input_tokens: input, output_tokens: output, total_tokens: total,
|
|
90
|
+
input_tokens_details: { cached_tokens: cachedInput }, output_tokens_details: { reasoning_tokens: reasoning } };
|
|
91
|
+
}
|
|
92
|
+
export function priceUsage(meters: TokenMeters, quotes: readonly PriceSnapshot[], serviceTier: string | null = null): Charge[] {
|
|
93
|
+
return quotes.map(quote => {
|
|
94
|
+
const missing: Charge['missing'] = [];
|
|
95
|
+
let schedule: Pick<PriceSnapshot, 'rules' | 'inputBands'> = quote;
|
|
96
|
+
if (quote.serviceTiers) {
|
|
97
|
+
if (serviceTier === null || !quote.serviceTiers[serviceTier]) missing.push('serviceTier');
|
|
98
|
+
else schedule = quote.serviceTiers[serviceTier];
|
|
99
|
+
}
|
|
100
|
+
let rules = schedule.rules;
|
|
101
|
+
if (schedule.inputBands?.length) {
|
|
102
|
+
if (meters.input === null) missing.push('input');
|
|
103
|
+
else for (const band of schedule.inputBands) if (meters.input >= band.from) rules = band.rules;
|
|
104
|
+
}
|
|
105
|
+
const unresolvedSchedule = missing.length > 0;
|
|
106
|
+
const lines = rules.map(rule => {
|
|
107
|
+
const detail = rule.meter.startsWith('detail:') ? meters.details?.[rule.meter.slice(7)] : undefined;
|
|
108
|
+
const quantity = rule.meter.startsWith('detail:') ? detail?.quantity ?? null : meters[rule.meter as Exclude<Meter, `detail:${string}`>];
|
|
109
|
+
const unit = rule.unit ?? 'token';
|
|
110
|
+
const known = !unresolvedSchedule && (!detail || detail.unit === unit);
|
|
111
|
+
const amount = known && rule.perMillion === 0 ? 0 : known && quantity !== null ? quantity * rule.perMillion / 1e6 : null;
|
|
112
|
+
if (amount === null && !unresolvedSchedule) missing.push(rule.meter);
|
|
113
|
+
return { meter: rule.meter, unit, quantity, perMillion: rule.perMillion, amount };
|
|
114
|
+
});
|
|
115
|
+
const knownAmount = lines.reduce((sum, line) => sum + (line.amount ?? 0), 0);
|
|
116
|
+
return { quote: structuredClone(quote), amount: missing.length ? null : knownAmount, knownAmount, missing: [...new Set(missing)], lines };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 单实例锁按 dataDir 隔离,防止同一数据目录被多个进程并发写入。
|
|
3
|
+
* 使用 wx 排他创建;陈旧锁通过临时文件、rename 和读回核对接管,竞争失败则拒绝启动。
|
|
4
|
+
* force 仅放行当前进程,不取得锁所有权,退出时不删除其他实例的锁。锁 startedAt 早于本机开机时刻时按陈旧锁处理,避免 PID 复用误判。
|
|
5
|
+
* verify 检测锁丢失或被接管并报告 error;丢失时补写,被接管时不争抢。
|
|
6
|
+
*/
|
|
7
|
+
import { closeSync, existsSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, writeSync } from 'node:fs';
|
|
8
|
+
import { uptime } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import type { Logger } from './types.ts';
|
|
11
|
+
import { nullLogger } from './util.ts';
|
|
12
|
+
|
|
13
|
+
export const INSTANCE_LOCK_FILE = 'instance.lock';
|
|
14
|
+
|
|
15
|
+
const VERIFY_INTERVAL_MS = 60_000;
|
|
16
|
+
|
|
17
|
+
interface LockPayload {
|
|
18
|
+
pid: number;
|
|
19
|
+
startedAt: string;
|
|
20
|
+
argv: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface InstanceLock {
|
|
24
|
+
/** 锁文件路径(诊断用) */
|
|
25
|
+
readonly file: string;
|
|
26
|
+
/** 使用 force 跳过占用检查时不取得锁所有权。 */
|
|
27
|
+
readonly owns: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 锁缺失时尝试重写;所有权记录变化时仅报告。
|
|
30
|
+
* 同一异常状态只报一次,恢复后允许再次报告。
|
|
31
|
+
*/
|
|
32
|
+
verify(): void;
|
|
33
|
+
/** 释放:只删自己写的那把锁 */
|
|
34
|
+
release(): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** signal 0 检查进程存在性和权限,不发送实际信号。 */
|
|
38
|
+
function isAlive(pid: number): boolean {
|
|
39
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
40
|
+
try {
|
|
41
|
+
process.kill(pid, 0);
|
|
42
|
+
return true;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// EPERM 说明进程存在,但当前进程无权向其发送信号。
|
|
45
|
+
return (e as NodeJS.ErrnoException).code === 'EPERM';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 启动时刻早于本机开机时刻时,存活的同号 PID 也不视为原锁所有者。
|
|
51
|
+
* 无法解析启动时刻时仅检查 PID。
|
|
52
|
+
*/
|
|
53
|
+
function ownerStillRunning(p: LockPayload): boolean {
|
|
54
|
+
if (!isAlive(p.pid)) return false;
|
|
55
|
+
const startedAt = Date.parse(p.startedAt);
|
|
56
|
+
if (Number.isNaN(startedAt)) return true;
|
|
57
|
+
const bootedAt = Date.now() - uptime() * 1000;
|
|
58
|
+
return startedAt >= bootedAt;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readPayload(file: string): LockPayload | null {
|
|
62
|
+
try {
|
|
63
|
+
const raw = JSON.parse(readFileSync(file, 'utf8')) as Partial<LockPayload>;
|
|
64
|
+
if (!Number.isInteger(raw.pid)) return null;
|
|
65
|
+
return {
|
|
66
|
+
pid: raw.pid as number,
|
|
67
|
+
startedAt: typeof raw.startedAt === 'string' ? raw.startedAt : '(未记录)',
|
|
68
|
+
argv: Array.isArray(raw.argv) ? raw.argv.map(String) : [],
|
|
69
|
+
};
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 排他创建;仅 EEXIST 返回 false,其他错误抛出。 */
|
|
76
|
+
function createExclusive(file: string, payload: LockPayload): boolean {
|
|
77
|
+
let fd: number;
|
|
78
|
+
try {
|
|
79
|
+
fd = openSync(file, 'wx');
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if ((e as NodeJS.ErrnoException).code === 'EEXIST') return false;
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
writeSync(fd, JSON.stringify(payload, null, 2), null, 'utf8');
|
|
86
|
+
} finally {
|
|
87
|
+
closeSync(fd);
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** 通过临时文件替换陈旧锁;读回 PID 核对所有权,替换或核对失败返回 false。 */
|
|
93
|
+
function takeOver(file: string, payload: LockPayload): boolean {
|
|
94
|
+
const tmp = `${file}.${payload.pid}.tmp`;
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf8');
|
|
97
|
+
renameSync(tmp, file);
|
|
98
|
+
} catch {
|
|
99
|
+
try { rmSync(tmp); } catch { /* 临时文件删不掉不影响判定 */ }
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
return readPayload(file)?.pid === payload.pid;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 占用锁对应的进程仍存在时拒绝启动,错误包含 PID 与启动时刻。
|
|
107
|
+
* force 可跳过检查并记录错误;该进程不取得锁所有权,退出时不删除锁。
|
|
108
|
+
*/
|
|
109
|
+
export function acquireInstanceLock(
|
|
110
|
+
dataDir: string,
|
|
111
|
+
opts: { force?: boolean; log?: Logger } = {},
|
|
112
|
+
): InstanceLock {
|
|
113
|
+
const log = opts.log ?? nullLogger();
|
|
114
|
+
const file = join(dataDir, INSTANCE_LOCK_FILE);
|
|
115
|
+
const mine: LockPayload = {
|
|
116
|
+
pid: process.pid,
|
|
117
|
+
startedAt: new Date().toISOString(),
|
|
118
|
+
argv: process.argv.slice(1),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
let owns = createExclusive(file, mine);
|
|
122
|
+
if (!owns) {
|
|
123
|
+
const existing = readPayload(file);
|
|
124
|
+
if (existing && ownerStillRunning(existing)) {
|
|
125
|
+
if (!opts.force) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`数据目录的锁记录指向运行中的进程(pid ${existing.pid},记录的启动时刻 ${existing.startedAt})。` +
|
|
128
|
+
`请检查锁记录及对应进程;需跳过占用检查时使用 --force-second-instance。锁文件:${file}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
log.error("锁记录对应的进程仍存在;已按 --force-second-instance 跳过检查,未取得锁所有权", {
|
|
132
|
+
otherPid: existing.pid,
|
|
133
|
+
otherStartedAt: existing.startedAt,
|
|
134
|
+
file,
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
// 锁无法读取,或其记录不满足存活所有者条件。
|
|
138
|
+
owns = takeOver(file, mine);
|
|
139
|
+
if (owns) {
|
|
140
|
+
log.warn("已接管无法读取或已失效的实例锁", {
|
|
141
|
+
stalePid: existing?.pid ?? null,
|
|
142
|
+
staleStartedAt: existing?.startedAt ?? null,
|
|
143
|
+
unreadable: existing === null,
|
|
144
|
+
file,
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
// 替换或读回核对失败,当前进程未获得锁所有权。
|
|
148
|
+
const winner = readPayload(file);
|
|
149
|
+
if (!opts.force) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`未取得数据目录的锁所有权(当前锁记录 PID: ${winner?.pid ?? '未知'})。` +
|
|
152
|
+
`请检查锁记录及对应进程;需跳过占用检查时使用 --force-second-instance。锁文件:${file}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
log.error("获取实例锁失败;已按 --force-second-instance 跳过检查,未取得锁所有权", {
|
|
156
|
+
otherPid: winner?.pid ?? null,
|
|
157
|
+
file,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let released = false;
|
|
164
|
+
/** 同一异常状态只报告一次;所有权恢复后重置。 */
|
|
165
|
+
let breachReported = false;
|
|
166
|
+
|
|
167
|
+
const verify = (): void => {
|
|
168
|
+
if (released || !owns) return;
|
|
169
|
+
const now = existsSync(file) ? readPayload(file) : null;
|
|
170
|
+
if (now?.pid === mine.pid) {
|
|
171
|
+
breachReported = false;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (breachReported) return;
|
|
175
|
+
breachReported = true;
|
|
176
|
+
if (now === null) {
|
|
177
|
+
// 缺失或无法读取的锁尝试重写,结果随日志报告。
|
|
178
|
+
const rewritten = createExclusive(file, mine) || takeOver(file, mine);
|
|
179
|
+
log.error("实例锁缺失或无法读取,已尝试重写;结果见 rewritten", {
|
|
180
|
+
file,
|
|
181
|
+
rewritten,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// 不覆盖已变化的所有权记录。
|
|
186
|
+
log.error("实例锁的所有权记录已变化", {
|
|
187
|
+
minePid: mine.pid,
|
|
188
|
+
nowPid: now.pid,
|
|
189
|
+
nowStartedAt: now.startedAt,
|
|
190
|
+
file,
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const timer = owns ? setInterval(verify, VERIFY_INTERVAL_MS) : null;
|
|
195
|
+
timer?.unref?.();
|
|
196
|
+
|
|
197
|
+
const release = (): void => {
|
|
198
|
+
if (released) return;
|
|
199
|
+
released = true;
|
|
200
|
+
if (timer) clearInterval(timer);
|
|
201
|
+
// 释放时移除退出钩子,避免重复获取锁时累积监听器。
|
|
202
|
+
process.off('exit', release);
|
|
203
|
+
if (!owns) return;
|
|
204
|
+
// 仅删除仍记录当前 PID 的锁。
|
|
205
|
+
const now = existsSync(file) ? readPayload(file) : null;
|
|
206
|
+
if (now?.pid !== mine.pid) return;
|
|
207
|
+
try {
|
|
208
|
+
rmSync(file);
|
|
209
|
+
} catch {
|
|
210
|
+
// 删除失败后由下次启动检查该锁。
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
process.on('exit', release);
|
|
214
|
+
return { file, owns, verify, release };
|
|
215
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子进程通过 IPC 发送 LogNote,父进程写入日志。
|
|
3
|
+
* 日志关联字段在子进程的异步作用域读取;时间使用子进程的 epoch 毫秒值,
|
|
4
|
+
* 由父进程按配置时区格式化。
|
|
5
|
+
*/
|
|
6
|
+
import type { LogAnchorFields, LogEmitOptions, LogError, Logger, LogLevel } from './types.ts';
|
|
7
|
+
import { currentAnchors } from './log-context.ts';
|
|
8
|
+
import { normalizeError, nowIso } from './util.ts';
|
|
9
|
+
|
|
10
|
+
export interface LogNote extends LogAnchorFields {
|
|
11
|
+
kind: 'log';
|
|
12
|
+
level: LogLevel;
|
|
13
|
+
area: string;
|
|
14
|
+
msg: string;
|
|
15
|
+
event?: string;
|
|
16
|
+
durMs?: number;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
err?: LogError;
|
|
19
|
+
/** 子进程侧的 epoch 毫秒 */
|
|
20
|
+
atMs: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ANCHOR_KEYS = ['sess', 'round', 'resp', 'call', 'ev', 'task'] as const;
|
|
24
|
+
|
|
25
|
+
export function createIpcLogger(send: (note: LogNote) => void, area = ''): Logger {
|
|
26
|
+
const emit = (level: LogLevel, msg: string, opts?: LogEmitOptions): void => {
|
|
27
|
+
const anchors = currentAnchors();
|
|
28
|
+
const note: LogNote = { kind: 'log', level, area, msg, atMs: Date.now() };
|
|
29
|
+
if (opts?.event !== undefined) note.event = opts.event;
|
|
30
|
+
if (opts?.durMs !== undefined) note.durMs = opts.durMs;
|
|
31
|
+
for (const key of ANCHOR_KEYS) {
|
|
32
|
+
const value = opts?.[key] ?? anchors[key];
|
|
33
|
+
if (value !== undefined) (note as unknown as Record<string, unknown>)[key] = value;
|
|
34
|
+
}
|
|
35
|
+
let data = opts?.data;
|
|
36
|
+
let err = opts?.err;
|
|
37
|
+
if (data instanceof Error) { err ??= data; data = undefined; }
|
|
38
|
+
else if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
39
|
+
const bag = data as Record<string, unknown>;
|
|
40
|
+
const carried = bag.err instanceof Error ? bag.err : bag.error instanceof Error ? bag.error : undefined;
|
|
41
|
+
if (carried) {
|
|
42
|
+
err ??= carried;
|
|
43
|
+
const { err: _e, error: _error, ...rest } = bag;
|
|
44
|
+
data = Object.keys(rest).length ? rest : undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (data !== undefined) {
|
|
48
|
+
try { JSON.stringify(data); } catch { data = String(data); }
|
|
49
|
+
note.data = data;
|
|
50
|
+
}
|
|
51
|
+
if (err !== undefined) note.err = normalizeError(err);
|
|
52
|
+
send(note);
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
trace: (m, d) => emit('trace', m, { data: d }),
|
|
56
|
+
debug: (m, d) => emit('debug', m, { data: d }),
|
|
57
|
+
info: (m, d) => emit('info', m, { data: d }),
|
|
58
|
+
warn: (m, d) => emit('warn', m, { data: d }),
|
|
59
|
+
error: (m, d) => emit('error', m, { data: d }),
|
|
60
|
+
emit,
|
|
61
|
+
child: (sub) => createIpcLogger(send, area ? `${area}.${sub}` : sub),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 主进程侧:把子进程的 note 按原字段落到 World 的 logger 下。 */
|
|
66
|
+
export function emitLogNote(root: Logger, note: LogNote, timezone: string): void {
|
|
67
|
+
const target = note.area ? root.child(note.area) : root;
|
|
68
|
+
const { kind: _kind, level, area: _area, msg, atMs, ...rest } = note;
|
|
69
|
+
target.emit(level, msg, { ...rest, ts: nowIso(timezone, new Date(atMs)) });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 把一个输出流按行落日志;跨 chunk 的半行拼起来,空行不记。 */
|
|
73
|
+
export function logLines(
|
|
74
|
+
stream: NodeJS.ReadableStream | null | undefined,
|
|
75
|
+
log: Logger,
|
|
76
|
+
level: LogLevel,
|
|
77
|
+
event: string,
|
|
78
|
+
): void {
|
|
79
|
+
if (!stream) return;
|
|
80
|
+
let carry = '';
|
|
81
|
+
stream.on('data', (chunk: Buffer | string) => {
|
|
82
|
+
const text = carry + chunk.toString();
|
|
83
|
+
const lines = text.split(/\r?\n/);
|
|
84
|
+
carry = lines.pop() ?? '';
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const trimmed = line.trim();
|
|
87
|
+
if (trimmed) log.emit(level, trimmed, { event });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
stream.on('end', () => {
|
|
91
|
+
const trimmed = carry.trim();
|
|
92
|
+
carry = '';
|
|
93
|
+
if (trimmed) log.emit(level, trimmed, { event });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** sidecar 的 stdout / stderr 进运行日志:区域 `<World>.stdio`,stdout 记 debug、stderr 记 warn。 */
|
|
98
|
+
export function logChildStdio(
|
|
99
|
+
child: { stdout?: NodeJS.ReadableStream | null; stderr?: NodeJS.ReadableStream | null },
|
|
100
|
+
log: Logger,
|
|
101
|
+
): void {
|
|
102
|
+
const stdio = log.child('stdio');
|
|
103
|
+
logLines(child.stdout, stdio, 'debug', 'stdout');
|
|
104
|
+
logLines(child.stderr, stdio, 'warn', 'stderr');
|
|
105
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console text uses the language supplied with each browser request.
|
|
3
|
+
* The initial default uses a recognized config.language, then CORTICO_LANGUAGE,
|
|
4
|
+
* then the system locale. Chinese locales select zh; other nonempty locales select en;
|
|
5
|
+
* an unavailable locale falls back to zh. The environment/locale result is cached once.
|
|
6
|
+
* The browser can override the initial <html lang> value and sends its choice in the
|
|
7
|
+
* console language header or query parameter. Panels receive ConsolePanelContext.language.
|
|
8
|
+
* Owners render request text in that language; missing translations use Chinese.
|
|
9
|
+
* This setting does not select or translate model-facing text.
|
|
10
|
+
*/
|
|
11
|
+
export type Language = 'zh' | 'en';
|
|
12
|
+
|
|
13
|
+
export const LANGUAGES: readonly Language[] = ['zh', 'en'];
|
|
14
|
+
|
|
15
|
+
export function isLanguage(value: unknown): value is Language {
|
|
16
|
+
return value === 'zh' || value === 'en';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** BCP 47 / POSIX tag → language. Empty input is "unknown", not English. */
|
|
20
|
+
export function languageOfLocale(tag: string | null | undefined): Language | undefined {
|
|
21
|
+
const t = (tag ?? '').trim().toLowerCase();
|
|
22
|
+
if (!t) return undefined;
|
|
23
|
+
return t === 'zh' || t.startsWith('zh-') || t.startsWith('zh_') ? 'zh' : 'en';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Pure form of the system read, for tests. */
|
|
27
|
+
export function detectLanguage(
|
|
28
|
+
env: Record<string, string | undefined>,
|
|
29
|
+
locale: string | null | undefined,
|
|
30
|
+
): Language {
|
|
31
|
+
const forced = env.CORTICO_LANGUAGE;
|
|
32
|
+
if (isLanguage(forced)) return forced;
|
|
33
|
+
return languageOfLocale(locale) ?? 'zh';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let systemMemo: Language | undefined;
|
|
37
|
+
|
|
38
|
+
/** The environment/locale read, performed once and then fixed for the process lifetime. */
|
|
39
|
+
export function systemLanguage(): Language {
|
|
40
|
+
if (systemMemo === undefined) {
|
|
41
|
+
// `process` is reached through globalThis so this file also type-checks under the DOM lib.
|
|
42
|
+
const env = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};
|
|
43
|
+
let locale: string | undefined;
|
|
44
|
+
try {
|
|
45
|
+
locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
46
|
+
} catch {
|
|
47
|
+
locale = undefined;
|
|
48
|
+
}
|
|
49
|
+
systemMemo = detectLanguage(env, locale);
|
|
50
|
+
}
|
|
51
|
+
return systemMemo;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A configured value wins over the system read; anything unrecognised is ignored. */
|
|
55
|
+
export function resolveLanguage(configured: unknown): Language {
|
|
56
|
+
return isLanguage(configured) ? configured : systemLanguage();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Select a language table; the type requires English to have the same keys as Chinese. */
|
|
60
|
+
export function pick<T>(language: Language, table: { readonly zh: T; readonly en: T }): T {
|
|
61
|
+
return language === 'en' ? table.en : table.zh;
|
|
62
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 轮次和工具调用边界设置日志关联字段;Logger 写入时从异步作用域读取。 */
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
|
|
4
|
+
export interface LogAnchors {
|
|
5
|
+
/** session 声明 id。 */
|
|
6
|
+
sess?: string;
|
|
7
|
+
/** 主循环轮次,进程内单调 */
|
|
8
|
+
round?: number;
|
|
9
|
+
/** 本轮的 response id */
|
|
10
|
+
resp?: string;
|
|
11
|
+
/** 正在执行的 tool_call id */
|
|
12
|
+
call?: string;
|
|
13
|
+
/** 事件游标 */
|
|
14
|
+
ev?: number;
|
|
15
|
+
/** World 任务号 */
|
|
16
|
+
task?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const storage = new AsyncLocalStorage<LogAnchors>();
|
|
20
|
+
|
|
21
|
+
/** 在 fn 及其派生的异步操作中合并关联字段;新字段覆盖同名继承值。 */
|
|
22
|
+
export function withAnchors<T>(anchors: LogAnchors, fn: () => T): T {
|
|
23
|
+
return storage.run({ ...storage.getStore(), ...anchors }, fn);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 修改最近一次 withAnchors 建立的作用域;没有作用域时不修改。 */
|
|
27
|
+
export function setAnchors(patch: LogAnchors): void {
|
|
28
|
+
const current = storage.getStore();
|
|
29
|
+
if (!current) return;
|
|
30
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
31
|
+
if (value === undefined) delete (current as Record<string, unknown>)[key];
|
|
32
|
+
else (current as Record<string, unknown>)[key] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function currentAnchors(): LogAnchors {
|
|
37
|
+
return storage.getStore() ?? {};
|
|
38
|
+
}
|