@opengeni/api-router 0.12.12 → 0.14.4
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/app.d.ts +18 -38
- package/dist/app.js +3 -1
- package/dist/auth/managed-auth.d.ts +35 -0
- package/dist/{chunk-5TULDPWX.js → chunk-36PW33ND.js} +4111 -1277
- package/dist/chunk-36PW33ND.js.map +1 -0
- package/dist/codex-redemption-security.d.ts +15 -0
- package/dist/github-access.d.ts +12 -0
- package/dist/github-browser-flow.d.ts +14 -0
- package/dist/http/auth.d.ts +3 -0
- package/dist/http/common.d.ts +3 -0
- package/dist/http/sse.d.ts +70 -0
- package/dist/index.d.ts +11 -13
- package/dist/index.js +1 -1
- package/dist/integrations/google-drive.d.ts +76 -0
- package/dist/integrations/oauth-client.d.ts +42 -0
- package/dist/integrations/provider-domain.d.ts +7 -0
- package/dist/integrations/slack-bot.d.ts +343 -0
- package/dist/mcp/documents.d.ts +8 -0
- package/dist/mcp/files.d.ts +5 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/session-view.d.ts +191 -0
- package/dist/mcp/toolspace.d.ts +61 -0
- package/dist/model-catalog.d.ts +39 -0
- package/dist/observability.d.ts +3 -0
- package/dist/routes/api-keys.d.ts +3 -0
- package/dist/routes/billing.d.ts +16 -0
- package/dist/routes/capabilities.d.ts +3 -0
- package/dist/routes/catalog-assets.d.ts +5 -0
- package/dist/routes/codex.d.ts +10 -0
- package/dist/routes/connections.d.ts +3 -0
- package/dist/routes/documents.d.ts +3 -0
- package/dist/routes/enrollments.d.ts +3 -0
- package/dist/routes/environments.d.ts +5 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/github.d.ts +3 -0
- package/dist/routes/insights.d.ts +3 -0
- package/dist/routes/install.d.ts +5 -0
- package/dist/routes/machines.d.ts +3 -0
- package/dist/routes/packs.d.ts +3 -0
- package/dist/routes/preference-registry.d.ts +5 -0
- package/dist/routes/rigs.d.ts +3 -0
- package/dist/routes/scheduled-tasks.d.ts +3 -0
- package/dist/routes/sessions.d.ts +15 -0
- package/dist/routes/social.d.ts +3 -0
- package/dist/routes/transcriptions.d.ts +3 -0
- package/dist/routes/workspace-capture.d.ts +59 -0
- package/dist/routes/workspace-instruction-policies.d.ts +3 -0
- package/dist/routes/workspace-state.d.ts +3 -0
- package/dist/routes/workspaces.d.ts +5 -0
- package/dist/sandbox/access.d.ts +21 -0
- package/dist/sandbox/auth-callout.d.ts +30 -0
- package/dist/sandbox/channel-a.d.ts +37 -0
- package/dist/sandbox/enrollment.d.ts +120 -0
- package/dist/sandbox/machines.d.ts +29 -0
- package/dist/sandbox/metrics-ingestion.d.ts +128 -0
- package/dist/sandbox/rematerialize.d.ts +24 -0
- package/dist/sandbox/viewer.d.ts +251 -0
- package/dist/transcription/providers/azure-openai.d.ts +9 -0
- package/dist/transcription/providers/codex-subscription.d.ts +9 -0
- package/dist/transcription/providers/openai.d.ts +9 -0
- package/dist/transcription/service.d.ts +10 -0
- package/dist/workspace-state-projection.d.ts +16 -0
- package/package.json +13 -13
- package/src/app.ts +67 -5
- package/src/integrations/google-drive.ts +869 -0
- package/src/integrations/oauth-client.ts +41 -6
- package/src/integrations/slack-bot.ts +756 -26
- package/src/mcp/server.ts +200 -3
- package/src/mcp/session-view.ts +1 -0
- package/src/mcp/toolspace.ts +17 -9
- package/src/routes/connections.ts +137 -7
- package/src/routes/documents.ts +13 -2
- package/src/routes/github.ts +7 -5
- package/src/routes/insights.ts +30 -0
- package/src/routes/preference-registry.ts +482 -0
- package/src/routes/sessions.ts +10 -0
- package/src/routes/transcriptions.ts +155 -0
- package/src/routes/workspace-state.ts +61 -0
- package/src/sandbox/channel-a.ts +2 -0
- package/src/sandbox/rematerialize.ts +111 -4
- package/src/sandbox/viewer.ts +29 -12
- package/src/transcription/providers/azure-openai.ts +45 -0
- package/src/transcription/providers/codex-subscription.ts +100 -0
- package/src/transcription/providers/openai.ts +93 -0
- package/src/transcription/service.ts +121 -0
- package/src/workspace-state-projection.ts +244 -0
- package/dist/chunk-5TULDPWX.js.map +0 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { resolveVoiceInputProviderRegistry, type Settings } from "@opengeni/config";
|
|
2
|
+
import { VOICE_INPUT_ACCEPTED_MIME_TYPES } from "@opengeni/contracts";
|
|
3
|
+
import {
|
|
4
|
+
filenameForMimeType,
|
|
5
|
+
isAcceptedMimeType,
|
|
6
|
+
normalizeMimeType,
|
|
7
|
+
type TranscriptionAvailabilityContext,
|
|
8
|
+
type TranscriptionProvider,
|
|
9
|
+
type TranscriptionService,
|
|
10
|
+
TranscriptionServiceError,
|
|
11
|
+
} from "@opengeni/core";
|
|
12
|
+
import type { Database } from "@opengeni/db";
|
|
13
|
+
import { createAzureOpenAiTranscriptionProvider } from "./providers/azure-openai";
|
|
14
|
+
import { createCodexSubscriptionTranscriptionProvider } from "./providers/codex-subscription";
|
|
15
|
+
import { createOpenAiTranscriptionProvider } from "./providers/openai";
|
|
16
|
+
|
|
17
|
+
export function createTranscriptionService(input: {
|
|
18
|
+
settings: Settings;
|
|
19
|
+
db: Database;
|
|
20
|
+
fetch?: typeof fetch;
|
|
21
|
+
codexFetch?: typeof fetch;
|
|
22
|
+
probeCodex?: (context?: TranscriptionAvailabilityContext) => boolean | Promise<boolean>;
|
|
23
|
+
}): TranscriptionService {
|
|
24
|
+
const providers: TranscriptionProvider[] = resolveVoiceInputProviderRegistry(input.settings).map(
|
|
25
|
+
(config) => {
|
|
26
|
+
switch (config.kind) {
|
|
27
|
+
case "openai":
|
|
28
|
+
return createOpenAiTranscriptionProvider({
|
|
29
|
+
...config,
|
|
30
|
+
...(input.fetch ? { fetch: input.fetch } : {}),
|
|
31
|
+
});
|
|
32
|
+
case "azure-openai":
|
|
33
|
+
return createAzureOpenAiTranscriptionProvider({
|
|
34
|
+
...config,
|
|
35
|
+
...(input.fetch ? { fetch: input.fetch } : {}),
|
|
36
|
+
});
|
|
37
|
+
case "codex-subscription":
|
|
38
|
+
return createCodexSubscriptionTranscriptionProvider({
|
|
39
|
+
settings: input.settings,
|
|
40
|
+
db: input.db,
|
|
41
|
+
...(input.codexFetch ? { fetch: input.codexFetch } : {}),
|
|
42
|
+
...(input.probeCodex ? { probe: input.probeCodex } : {}),
|
|
43
|
+
});
|
|
44
|
+
default:
|
|
45
|
+
throw new Error("Unsupported voice-input provider.");
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
const limits = {
|
|
50
|
+
maxDurationSeconds: input.settings.voiceInputMaxDurationSeconds,
|
|
51
|
+
maxSizeBytes: input.settings.voiceInputMaxSizeBytes,
|
|
52
|
+
acceptedMimeTypes: [...VOICE_INPUT_ACCEPTED_MIME_TYPES],
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
limits: () => limits,
|
|
56
|
+
async available(context) {
|
|
57
|
+
return (await Promise.all(providers.map((provider) => provider.available(context)))).some(
|
|
58
|
+
Boolean,
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
async transcribe(request) {
|
|
62
|
+
const mimeType = normalizeMimeType(request.mimeType);
|
|
63
|
+
if (!isAcceptedMimeType(mimeType, limits.acceptedMimeTypes)) {
|
|
64
|
+
throw new TranscriptionServiceError({
|
|
65
|
+
code: "not_supported",
|
|
66
|
+
message: "Unsupported audio format.",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (request.audio.byteLength > limits.maxSizeBytes) {
|
|
70
|
+
throw new TranscriptionServiceError({
|
|
71
|
+
code: "too_large",
|
|
72
|
+
message: "Audio is too large.",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
request.durationSeconds !== undefined &&
|
|
77
|
+
(!Number.isFinite(request.durationSeconds) ||
|
|
78
|
+
request.durationSeconds < 0 ||
|
|
79
|
+
request.durationSeconds > limits.maxDurationSeconds)
|
|
80
|
+
) {
|
|
81
|
+
throw new TranscriptionServiceError({
|
|
82
|
+
code: "invalid_audio",
|
|
83
|
+
message: "Invalid audio duration.",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const provider = await firstAvailable(providers, {
|
|
87
|
+
workspaceId: request.workspaceId,
|
|
88
|
+
});
|
|
89
|
+
if (!provider) {
|
|
90
|
+
throw new TranscriptionServiceError({
|
|
91
|
+
code: "unavailable",
|
|
92
|
+
message: "Transcription is unavailable.",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const startedAt = performance.now();
|
|
96
|
+
const result = await provider.transcribe({
|
|
97
|
+
audio: request.audio,
|
|
98
|
+
mimeType,
|
|
99
|
+
filename: filenameForMimeType(mimeType),
|
|
100
|
+
workspaceId: request.workspaceId,
|
|
101
|
+
signal: request.signal,
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
...result,
|
|
105
|
+
providerId: provider.id,
|
|
106
|
+
audioSeconds: request.durationSeconds ?? 0,
|
|
107
|
+
latencyMs: Math.round(performance.now() - startedAt),
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function firstAvailable(
|
|
114
|
+
providers: readonly TranscriptionProvider[],
|
|
115
|
+
context: TranscriptionAvailabilityContext,
|
|
116
|
+
) {
|
|
117
|
+
for (const provider of providers) {
|
|
118
|
+
if (await provider.available(context)) return provider;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import {
|
|
2
|
+
KnowledgeMemoryKind,
|
|
3
|
+
KnowledgeMemoryStatus,
|
|
4
|
+
WORKSPACE_STATE_BASE_NAME_MAX_CHARS,
|
|
5
|
+
WORKSPACE_STATE_MAX_ACTIVE_POLICY_HEADS,
|
|
6
|
+
WORKSPACE_STATE_MAX_BASES,
|
|
7
|
+
WORKSPACE_STATE_MAX_GAPS,
|
|
8
|
+
WORKSPACE_STATE_MAX_TOPICS,
|
|
9
|
+
WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT,
|
|
10
|
+
WORKSPACE_STATE_TOPIC_MAX_CHARS,
|
|
11
|
+
WorkspaceStateResponse,
|
|
12
|
+
type WorkspaceInstructionPolicyListResponse,
|
|
13
|
+
type WorkspaceStateGap,
|
|
14
|
+
type WorkspaceStateMemoryKindCounts,
|
|
15
|
+
type WorkspaceStateMemoryStatusCounts,
|
|
16
|
+
type WorkspaceStateResponse as WorkspaceStateResponseType,
|
|
17
|
+
} from "@opengeni/contracts";
|
|
18
|
+
import type { WorkspaceStateMemoryRecord } from "@opengeni/db";
|
|
19
|
+
import type { DocumentInventory } from "@opengeni/documents";
|
|
20
|
+
|
|
21
|
+
type KnowledgeProjectionInput = {
|
|
22
|
+
documents: DocumentInventory;
|
|
23
|
+
memories: WorkspaceStateMemoryRecord[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type WorkspaceStateProjectionInput = {
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
generatedAt: string;
|
|
29
|
+
workspaceAgentInstructions: string | null;
|
|
30
|
+
policies: WorkspaceInstructionPolicyListResponse;
|
|
31
|
+
knowledge: KnowledgeProjectionInput | null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function emptyMemoryStatusCounts(): WorkspaceStateMemoryStatusCounts {
|
|
35
|
+
return Object.fromEntries(
|
|
36
|
+
KnowledgeMemoryStatus.options.map((status) => [status, 0]),
|
|
37
|
+
) as WorkspaceStateMemoryStatusCounts;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function emptyMemoryKindCounts(): WorkspaceStateMemoryKindCounts {
|
|
41
|
+
return Object.fromEntries(
|
|
42
|
+
KnowledgeMemoryKind.options.map((kind) => [kind, 0]),
|
|
43
|
+
) as WorkspaceStateMemoryKindCounts;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function boundedLabel(value: string, maxChars: number, fallback: string): string {
|
|
47
|
+
const normalized = value.normalize("NFKC").trim().replace(/\s+/gu, " ");
|
|
48
|
+
return (normalized || fallback).slice(0, maxChars);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function newestTimestamp(values: Array<string | null>): string | null {
|
|
52
|
+
return values.reduce<string | null>((latest, value) => {
|
|
53
|
+
if (!value) return latest;
|
|
54
|
+
return latest === null || value > latest ? value : latest;
|
|
55
|
+
}, null);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function comparePolicyTargets(
|
|
59
|
+
left: WorkspaceInstructionPolicyListResponse["activeHeads"][number],
|
|
60
|
+
right: WorkspaceInstructionPolicyListResponse["activeHeads"][number],
|
|
61
|
+
): number {
|
|
62
|
+
return `${left.kind}:${left.scope}:${left.roleKey ?? ""}`.localeCompare(
|
|
63
|
+
`${right.kind}:${right.scope}:${right.roleKey ?? ""}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function policyProjection(input: WorkspaceStateProjectionInput) {
|
|
68
|
+
const sortedHeads = [...input.policies.activeHeads].sort(comparePolicyTargets);
|
|
69
|
+
const activeHeads = sortedHeads.slice(0, WORKSPACE_STATE_MAX_ACTIVE_POLICY_HEADS).map((head) => ({
|
|
70
|
+
kind: head.kind,
|
|
71
|
+
scope: head.scope,
|
|
72
|
+
roleKey: head.roleKey,
|
|
73
|
+
revisionId: head.revisionId,
|
|
74
|
+
revision: head.revision,
|
|
75
|
+
contentHash: head.contentHash,
|
|
76
|
+
activationVersion: head.activationVersion,
|
|
77
|
+
activatedAt: head.activatedAt,
|
|
78
|
+
}));
|
|
79
|
+
const newestRevision = input.policies.revisions[0] ?? null;
|
|
80
|
+
return {
|
|
81
|
+
authority: "workspace_instruction_policy_heads" as const,
|
|
82
|
+
activeHeads,
|
|
83
|
+
activeHeadsTruncated: sortedHeads.length > activeHeads.length,
|
|
84
|
+
latestRevision: newestRevision
|
|
85
|
+
? {
|
|
86
|
+
kind: newestRevision.kind,
|
|
87
|
+
scope: newestRevision.scope,
|
|
88
|
+
roleKey: newestRevision.roleKey,
|
|
89
|
+
revisionId: newestRevision.id,
|
|
90
|
+
revision: newestRevision.revision,
|
|
91
|
+
contentHash: newestRevision.contentHash,
|
|
92
|
+
provenanceSource: newestRevision.provenance.source,
|
|
93
|
+
state: input.policies.activeHeads.some((head) => head.revisionId === newestRevision.id)
|
|
94
|
+
? ("active" as const)
|
|
95
|
+
: ("inactive" as const),
|
|
96
|
+
createdAt: newestRevision.createdAt,
|
|
97
|
+
}
|
|
98
|
+
: null,
|
|
99
|
+
legacyRuntime: {
|
|
100
|
+
source: input.workspaceAgentInstructions
|
|
101
|
+
? ("workspace_override" as const)
|
|
102
|
+
: ("deployment_default" as const),
|
|
103
|
+
workspaceOverrideConfigured: Boolean(input.workspaceAgentInstructions),
|
|
104
|
+
},
|
|
105
|
+
runtimeComposition: { status: "not_implemented" as const },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function availableKnowledgeProjection(knowledge: KnowledgeProjectionInput) {
|
|
110
|
+
const inventory = knowledge.documents;
|
|
111
|
+
const selectedBases = inventory.bases.slice(0, WORKSPACE_STATE_MAX_BASES);
|
|
112
|
+
const aggregateStatuses = { ...inventory.statusCounts };
|
|
113
|
+
const aggregateSources = { ...inventory.sourceKindCounts };
|
|
114
|
+
const topicCounts = new Map<string, number>();
|
|
115
|
+
const projectedBases = selectedBases.map((base) => {
|
|
116
|
+
return {
|
|
117
|
+
id: base.id,
|
|
118
|
+
name: boundedLabel(base.name, WORKSPACE_STATE_BASE_NAME_MAX_CHARS, "Untitled base"),
|
|
119
|
+
visibleDocumentCount: base.visibleDocumentCount,
|
|
120
|
+
statusCounts: base.statusCounts,
|
|
121
|
+
latestUpdatedAt: base.latestUpdatedAt,
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
for (const topic of inventory.topics) {
|
|
126
|
+
const name = boundedLabel(topic.name, WORKSPACE_STATE_TOPIC_MAX_CHARS, "Unlabeled topic");
|
|
127
|
+
topicCounts.set(name, (topicCounts.get(name) ?? 0) + topic.documentCount);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sortedTopics = [...topicCounts.entries()]
|
|
131
|
+
.sort(
|
|
132
|
+
([leftName, leftCount], [rightName, rightCount]) =>
|
|
133
|
+
rightCount - leftCount || leftName.localeCompare(rightName),
|
|
134
|
+
)
|
|
135
|
+
.map(([name, documentCount]) => ({ name, documentCount }));
|
|
136
|
+
const topics = sortedTopics.slice(0, WORKSPACE_STATE_MAX_TOPICS);
|
|
137
|
+
const memories = knowledge.memories.slice(0, WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT);
|
|
138
|
+
const memoryStatuses = emptyMemoryStatusCounts();
|
|
139
|
+
const memoryKinds = emptyMemoryKindCounts();
|
|
140
|
+
for (const memory of memories) {
|
|
141
|
+
memoryStatuses[memory.status] += 1;
|
|
142
|
+
memoryKinds[memory.kind] += 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const inspectedVisibleDocumentCount = inventory.visibleDocumentCount;
|
|
146
|
+
const basesTruncated = inventory.baseCount > projectedBases.length;
|
|
147
|
+
const memoryLimitReached = memories.length === WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT;
|
|
148
|
+
const inventoryPartial = basesTruncated || inventory.topicsTruncated || memoryLimitReached;
|
|
149
|
+
const gaps: WorkspaceStateGap[] = [];
|
|
150
|
+
const addGap = (gap: WorkspaceStateGap): void => {
|
|
151
|
+
if (gaps.length < WORKSPACE_STATE_MAX_GAPS) gaps.push(gap);
|
|
152
|
+
};
|
|
153
|
+
if (inventory.baseCount === 0) {
|
|
154
|
+
addGap({ code: "no_document_bases", severity: "info", relatedCount: 0 });
|
|
155
|
+
} else if (inspectedVisibleDocumentCount === 0) {
|
|
156
|
+
addGap({ code: "no_visible_documents", severity: "info", relatedCount: 0 });
|
|
157
|
+
}
|
|
158
|
+
if (aggregateStatuses.failed > 0) {
|
|
159
|
+
addGap({
|
|
160
|
+
code: "failed_documents",
|
|
161
|
+
severity: "warning",
|
|
162
|
+
relatedCount: aggregateStatuses.failed,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const processingCount = aggregateStatuses.queued + aggregateStatuses.indexing;
|
|
166
|
+
if (processingCount > 0) {
|
|
167
|
+
addGap({
|
|
168
|
+
code: "processing_documents",
|
|
169
|
+
severity: "info",
|
|
170
|
+
relatedCount: processingCount,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (aggregateStatuses.ready > 0 && sortedTopics.length === 0) {
|
|
174
|
+
addGap({
|
|
175
|
+
code: "missing_topic_coverage",
|
|
176
|
+
severity: "info",
|
|
177
|
+
relatedCount: aggregateStatuses.ready,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (memories.length === 0) {
|
|
181
|
+
addGap({ code: "no_memory_records", severity: "info", relatedCount: 0 });
|
|
182
|
+
}
|
|
183
|
+
if (memoryStatuses.proposed > 0) {
|
|
184
|
+
addGap({
|
|
185
|
+
code: "pending_memory_review",
|
|
186
|
+
severity: "info",
|
|
187
|
+
relatedCount: memoryStatuses.proposed,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (inventoryPartial) {
|
|
191
|
+
addGap({ code: "partial_inventory", severity: "info", relatedCount: null });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
availability: "available" as const,
|
|
196
|
+
coverage: inventoryPartial ? ("partial" as const) : ("complete" as const),
|
|
197
|
+
baseCount: inventory.baseCount,
|
|
198
|
+
bases: projectedBases,
|
|
199
|
+
basesTruncated,
|
|
200
|
+
inspectedVisibleDocumentCount,
|
|
201
|
+
documentStatusCounts: aggregateStatuses,
|
|
202
|
+
sourceKindCounts: aggregateSources,
|
|
203
|
+
topics,
|
|
204
|
+
topicsTruncated: inventory.topicsTruncated || sortedTopics.length > topics.length,
|
|
205
|
+
latestDocumentUpdatedAt: inventory.latestUpdatedAt,
|
|
206
|
+
memorySample: {
|
|
207
|
+
recordCount: memories.length,
|
|
208
|
+
sampleLimit: WORKSPACE_STATE_MEMORY_SAMPLE_LIMIT,
|
|
209
|
+
limitReached: memoryLimitReached,
|
|
210
|
+
statusCounts: memoryStatuses,
|
|
211
|
+
kindCounts: memoryKinds,
|
|
212
|
+
preferenceAuthority: {
|
|
213
|
+
kindCountSource: "knowledge_memories_legacy_observations" as const,
|
|
214
|
+
activeAuthority: "structured_preference_registry" as const,
|
|
215
|
+
},
|
|
216
|
+
latestUpdatedAt: newestTimestamp(memories.map((memory) => memory.updatedAt)),
|
|
217
|
+
},
|
|
218
|
+
gaps,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function projectWorkspaceState(
|
|
223
|
+
input: WorkspaceStateProjectionInput,
|
|
224
|
+
): WorkspaceStateResponseType {
|
|
225
|
+
return WorkspaceStateResponse.parse({
|
|
226
|
+
workspaceId: input.workspaceId,
|
|
227
|
+
generatedAt: input.generatedAt,
|
|
228
|
+
truth: {
|
|
229
|
+
current: { source: "read_time_projection", capturedAt: input.generatedAt },
|
|
230
|
+
policySnapshot: {
|
|
231
|
+
status: "not_captured",
|
|
232
|
+
reason: "workspace_instruction_policy_snapshot_not_implemented",
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
policy: policyProjection(input),
|
|
236
|
+
knowledge: input.knowledge
|
|
237
|
+
? availableKnowledgeProjection(input.knowledge)
|
|
238
|
+
: {
|
|
239
|
+
availability: "unavailable",
|
|
240
|
+
reason: "missing_permission",
|
|
241
|
+
requiredPermission: "documents:search",
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|