@noodleseed/one 0.168.0 → 0.170.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/capabilities.d.ts +19 -0
- package/dist/commands/capabilities.d.ts.map +1 -0
- package/dist/commands/capabilities.js +128 -0
- package/dist/commands/capabilities.js.map +1 -0
- package/dist/commands/deployment-delete-ops.d.ts +12 -0
- package/dist/commands/deployment-delete-ops.d.ts.map +1 -0
- package/dist/commands/deployment-delete-ops.js +230 -0
- package/dist/commands/deployment-delete-ops.js.map +1 -0
- package/dist/commands/deployment-delete-recovery.d.ts +35 -0
- package/dist/commands/deployment-delete-recovery.d.ts.map +1 -0
- package/dist/commands/deployment-delete-recovery.js +99 -0
- package/dist/commands/deployment-delete-recovery.js.map +1 -0
- package/dist/commands/deployments-ops.d.ts +2 -1
- package/dist/commands/deployments-ops.d.ts.map +1 -1
- package/dist/commands/deployments-ops.js +5 -1
- package/dist/commands/deployments-ops.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-mode/plugin-operation-contract.d.ts +2 -2
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +4 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +3 -2
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.d.ts +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-interactive.js +4 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +2 -2
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +3 -1
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -0
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.d.ts +2 -1
- package/node_modules/@noodle-borg/authoring/dist/recording.js +29 -1
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -0
- package/node_modules/@noodle-borg/authoring/dist/server.js +5 -1
- package/node_modules/@noodle-borg/authoring/dist/web-capabilities.d.ts +17 -0
- package/node_modules/@noodle-borg/authoring/dist/web-capabilities.js +15 -0
- package/node_modules/@noodle-borg/authoring/package.json +1 -0
- package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-observability.js +102 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-tenant-resources.js +27 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/assemble.js +3 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/compile.js +2 -0
- package/node_modules/@noodle-borg/compiler/dist/fulfilment-emit.js +18 -3
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +34 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +9 -2
- package/node_modules/@noodle-borg/compiler/dist/web-capabilities.js +55 -0
- package/node_modules/@noodle-borg/compiler/package.json +1 -0
- package/node_modules/@noodle-borg/connector-http/dist/ssrf.js +5 -0
- package/node_modules/@noodle-borg/control-plane/dist/delete-deployment.d.ts +44 -0
- package/node_modules/@noodle-borg/control-plane/dist/delete-deployment.js +105 -0
- package/node_modules/@noodle-borg/control-plane/dist/deployment-deletion-contracts.d.ts +20 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/bounded-body.js +38 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/first-party.js +2 -3
- package/node_modules/@noodle-borg/knowledge-crawl/dist/html-text.js +10 -2
- package/node_modules/@noodle-borg/knowledge-crawl/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/index.js +1 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/page-reader.d.ts +26 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/page-reader.js +127 -0
- package/node_modules/@noodle-borg/knowledge-crawl/dist/robots.js +48 -23
- package/node_modules/@noodle-borg/knowledge-crawl/package.json +1 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/budget.d.ts +29 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/budget.js +124 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/contracts.d.ts +212 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/contracts.js +113 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/errors.d.ts +8 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/errors.js +21 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/executor.d.ts +26 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/executor.js +156 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/limits.d.ts +15 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/limits.js +15 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-contracts.d.ts +159 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-contracts.js +69 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-route.d.ts +5 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/operator-route.js +160 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/policy-store.d.ts +34 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/policy-store.js +61 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/portable.d.ts +10 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/portable.js +10 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/service.d.ts +83 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/service.js +139 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/urls.d.ts +3 -0
- package/node_modules/@noodle-borg/managed-capabilities/dist/urls.js +30 -0
- package/node_modules/@noodle-borg/managed-capabilities/package.json +49 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +11 -0
- package/node_modules/@noodle-borg/runtime/dist/confirmation.js +29 -0
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +3 -0
- package/node_modules/@noodle-borg/runtime/dist/execute.js +6 -1
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +2 -1
- package/node_modules/@noodle-borg/runtime/dist/index.js +2 -1
- package/node_modules/@noodle-borg/runtime/dist/interactive.js +10 -1
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +3 -0
- package/node_modules/@noodle-borg/runtime/dist/result-meta.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/result-meta.js +6 -0
- package/node_modules/@noodle-borg/runtime/dist/result.d.ts +4 -0
- package/node_modules/@noodle-borg/runtime/dist/web-capabilities.d.ts +71 -0
- package/node_modules/@noodle-borg/runtime/dist/web-capabilities.js +41 -0
- package/node_modules/@noodle-borg/runtime/package.json +1 -0
- package/node_modules/@noodle-borg/service/dist/deployment-deletion.js +40 -0
- package/node_modules/@noodle-borg/service/dist/local-options.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/local-web-capabilities.js +13 -0
- package/node_modules/@noodle-borg/service/dist/mcp-protocol-runtime.js +21 -0
- package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +13 -0
- package/node_modules/@noodle-borg/service/dist/registry-compile.js +9 -0
- package/node_modules/@noodle-borg/service/dist/registry-deletion.js +75 -0
- package/node_modules/@noodle-borg/service/dist/registry-state.js +4 -2
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -0
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/registry.d.ts +2 -0
- package/node_modules/@noodle-borg/service/dist/registry.js +10 -23
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +5 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +7 -4
- package/node_modules/@noodle-borg/service/dist/routes/deploy-dispatch.js +15 -1
- package/node_modules/@noodle-borg/service/dist/routes/deployment-delete.js +53 -0
- package/node_modules/@noodle-borg/service/dist/serve-local.js +14 -0
- package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +10 -0
- package/node_modules/@noodle-borg/service/dist/service.js +7 -1
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -1
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +8 -0
- package/node_modules/@noodle-borg/service/dist/store/json-file-deletion.js +64 -0
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -1
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +23 -5
- package/node_modules/@noodle-borg/service/dist/store.d.ts +5 -1
- package/node_modules/@noodle-borg/service/dist/web-capabilities.js +39 -0
- package/node_modules/@noodle-borg/service/package.json +3 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/deployment-deletion.d.ts +29 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/deployment-deletion.js +33 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +2 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +2 -0
- package/node_modules/@noodle-borg/wire-contracts/package.json +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const ERRORS = {
|
|
2
|
+
deployment_not_found: 'Deployment not found.',
|
|
3
|
+
version_not_found: 'Version not found.',
|
|
4
|
+
active_deployment: 'Roll back to another deployment before deleting the live deployment, or delete the whole version.',
|
|
5
|
+
deployment_locked: 'Unlock this version before deleting it.',
|
|
6
|
+
app_archived: 'Restore the archived app before deleting its deployments.',
|
|
7
|
+
deployment_delete_conflict: 'The deployment history changed. Refresh and review it before deleting.',
|
|
8
|
+
};
|
|
9
|
+
/** One owner-authorized deletion path for every host; stores commit the exact selection atomically. */
|
|
10
|
+
export async function deleteDeploymentOperation(deps, input) {
|
|
11
|
+
const { actor: identity, scope: route } = input;
|
|
12
|
+
const org = route.kind === 'version' ? route.target.org : route.org;
|
|
13
|
+
const member = identity.superAdmin
|
|
14
|
+
? undefined
|
|
15
|
+
: await deps.controlPlane.getOrgMember({ org, subject: identity.subject });
|
|
16
|
+
if (identity.developerGrantId !== undefined ||
|
|
17
|
+
(!identity.superAdmin && member?.role !== 'owner')) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
status: 403,
|
|
21
|
+
code: 'organization_owner_required',
|
|
22
|
+
error: 'Only an organization owner can delete deployments or versions.',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
let target;
|
|
26
|
+
let selection;
|
|
27
|
+
if (route.kind === 'deployment') {
|
|
28
|
+
const record = await deps.registry.getDeployment(route.org, route.deploymentId);
|
|
29
|
+
if (!record)
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
status: 404,
|
|
33
|
+
code: 'deployment_not_found',
|
|
34
|
+
error: ERRORS.deployment_not_found,
|
|
35
|
+
};
|
|
36
|
+
target = { org: record.orgSlug, app: record.appSlug, env: record.environment };
|
|
37
|
+
selection = { kind: 'deployment', deploymentId: route.deploymentId };
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (!input.expectedDeploymentIds?.length)
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
status: 400,
|
|
44
|
+
code: 'invalid_deletion_confirmation',
|
|
45
|
+
error: 'Invalid version deletion confirmation.',
|
|
46
|
+
};
|
|
47
|
+
target = route.target;
|
|
48
|
+
selection = {
|
|
49
|
+
kind: 'version',
|
|
50
|
+
expectedDeploymentIds: input.expectedDeploymentIds,
|
|
51
|
+
...(route.serverVersion === undefined ? {} : { serverVersion: route.serverVersion }),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const result = await deps.registry.deleteDeployments(target, selection);
|
|
55
|
+
const status = result.ok
|
|
56
|
+
? 200
|
|
57
|
+
: result.code === 'deployment_not_found' || result.code === 'version_not_found'
|
|
58
|
+
? 404
|
|
59
|
+
: 409;
|
|
60
|
+
let auditRecorded = true;
|
|
61
|
+
try {
|
|
62
|
+
await deps.audit.emit({
|
|
63
|
+
eventType: result.ok
|
|
64
|
+
? route.kind === 'version'
|
|
65
|
+
? 'deployment.version.deleted'
|
|
66
|
+
: 'deployment.deleted'
|
|
67
|
+
: 'deployment.delete.rejected',
|
|
68
|
+
org: target.org,
|
|
69
|
+
app: target.app,
|
|
70
|
+
env: target.env,
|
|
71
|
+
...(route.kind === 'deployment' ? { deploymentId: route.deploymentId } : {}),
|
|
72
|
+
actorSubject: identity.subject,
|
|
73
|
+
...(identity.email === undefined ? {} : { actorEmail: identity.email }),
|
|
74
|
+
decision: result.ok ? 'allow' : 'deny',
|
|
75
|
+
status,
|
|
76
|
+
...(!result.ok ? { reasonCode: result.code } : {}),
|
|
77
|
+
details: {
|
|
78
|
+
scope: route.kind,
|
|
79
|
+
...(route.kind === 'version' ? { serverVersion: route.serverVersion ?? 'legacy' } : {}),
|
|
80
|
+
...(result.ok ? { deletedDeployments: result.deleted.length } : {}),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Deletion has already committed. An audit outage must not claim the records still exist.
|
|
86
|
+
auditRecorded = false;
|
|
87
|
+
}
|
|
88
|
+
if (!result.ok)
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
status: status,
|
|
92
|
+
code: result.code,
|
|
93
|
+
error: ERRORS[result.code],
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
view: {
|
|
98
|
+
ok: true,
|
|
99
|
+
target,
|
|
100
|
+
deletedDeploymentIds: result.deleted.map((record) => record.deploymentId),
|
|
101
|
+
auditRecorded,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=delete-deployment.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type DeploymentDeleteSelection = {
|
|
2
|
+
readonly kind: 'deployment';
|
|
3
|
+
readonly deploymentId: string;
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: 'version';
|
|
6
|
+
readonly serverVersion?: string;
|
|
7
|
+
readonly expectedDeploymentIds: readonly string[];
|
|
8
|
+
};
|
|
9
|
+
export type DeploymentDeleteResult<Record extends {
|
|
10
|
+
readonly deploymentId: string;
|
|
11
|
+
} = {
|
|
12
|
+
readonly deploymentId: string;
|
|
13
|
+
}> = {
|
|
14
|
+
readonly ok: true;
|
|
15
|
+
readonly deleted: readonly Record[];
|
|
16
|
+
} | {
|
|
17
|
+
readonly ok: false;
|
|
18
|
+
readonly code: 'deployment_not_found' | 'version_not_found' | 'active_deployment' | 'deployment_locked' | 'app_archived' | 'deployment_delete_conflict';
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=deployment-deletion-contracts.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { type AppPurgeReconciliationActor, type AppPurgeReconciliationConflictCode, AppPurgeReconciliationError, type AppPurgeReconciliationOperator, } from './app-purge-reconciliation-port.js';
|
|
2
2
|
export type { ActiveMcpSubdomainClaim, ChangeMcpSubdomainInput, ControlPlaneIdentity, CreateOrgWithOwnerInput, McpSubdomainMutationResult, McpSubdomainSetting, OrganizationStore, OrgDomainRecord, OrgInvitationRecord, OrgMemberRecord, OrgOpenAIAppsChallengeRecord, OrgRecord, OrgRole, PersonalWorkspaceProvisionInput, PersonalWorkspaceProvisionResult, SignupAllowlistKind, SignupAllowlistRecord, WelcomeEmailRecord, } from './contracts.js';
|
|
3
3
|
export { PersonalWorkspaceOwnerMutationError } from './contracts.js';
|
|
4
|
+
export { type DeploymentDeleteOperationResult, type DeploymentDeleteScope, type DeploymentDeletionDependencies, type DeploymentDeletionRegistry, deleteDeploymentOperation, } from './delete-deployment.js';
|
|
4
5
|
export { allowAllGate, bearerToken, type ControlPlaneAuthResult, type DeployAuthGate, } from './deploy-auth.js';
|
|
5
6
|
export { CompositeControlPlaneGate, type ControlPlaneSignupMode, GoogleControlPlaneGate, type GoogleControlPlaneGateOptions, type GoogleIdTokenVerifier, GoogleWorkloadControlPlaneGate, type GoogleWorkloadControlPlaneGateOptions, NoodleOAuthControlPlaneGate, type NoodleOAuthControlPlaneGateOptions, type SignupAuthorizer, } from './deploy-gates.js';
|
|
7
|
+
export type { DeploymentDeleteResult, DeploymentDeleteSelection, } from './deployment-deletion-contracts.js';
|
|
6
8
|
export { InMemoryControlPlaneStore } from './in-memory-control-plane-store.js';
|
|
7
9
|
export { bindInMemoryOrganizationStore, type InMemoryOrganizationOperations, InMemoryOrganizationStore, } from './in-memory-organization-store.js';
|
|
8
10
|
export { InMemoryMcpSubdomainClaimStore, McpSubdomainCooldownError, McpSubdomainIdempotencyConflictError, McpSubdomainOwnerRequiredError, type McpSubdomainRouteRef, McpSubdomainUnavailableError, mcpSubdomainEndpointOptions, type ResolvedMcpTenantRef, resolveMcpSubdomainTenant, } from './mcp-subdomain-claims.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { AppPurgeReconciliationError, } from './app-purge-reconciliation-port.js';
|
|
2
2
|
export { PersonalWorkspaceOwnerMutationError } from './contracts.js';
|
|
3
|
+
export { deleteDeploymentOperation, } from './delete-deployment.js';
|
|
3
4
|
export { allowAllGate, bearerToken, } from './deploy-auth.js';
|
|
4
5
|
export { CompositeControlPlaneGate, GoogleControlPlaneGate, GoogleWorkloadControlPlaneGate, NoodleOAuthControlPlaneGate, } from './deploy-gates.js';
|
|
5
6
|
export { InMemoryControlPlaneStore } from './in-memory-control-plane-store.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Read decoded response bytes with bounded memory, cancellation and body cleanup on every path. */
|
|
2
|
+
export async function readBoundedBody(response, maxBytes, signal) {
|
|
3
|
+
const reader = response.body?.getReader();
|
|
4
|
+
if (reader === undefined)
|
|
5
|
+
return new ArrayBuffer(0);
|
|
6
|
+
const chunks = [];
|
|
7
|
+
let size = 0;
|
|
8
|
+
const cancel = () => {
|
|
9
|
+
void reader.cancel().catch(() => { });
|
|
10
|
+
};
|
|
11
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
12
|
+
try {
|
|
13
|
+
for (;;) {
|
|
14
|
+
signal?.throwIfAborted();
|
|
15
|
+
const next = await reader.read();
|
|
16
|
+
signal?.throwIfAborted();
|
|
17
|
+
if (next.done)
|
|
18
|
+
break;
|
|
19
|
+
size += next.value.byteLength;
|
|
20
|
+
if (size > maxBytes)
|
|
21
|
+
throw new Error('response_size_exceeded');
|
|
22
|
+
chunks.push(next.value);
|
|
23
|
+
}
|
|
24
|
+
const result = new Uint8Array(size);
|
|
25
|
+
let offset = 0;
|
|
26
|
+
for (const chunk of chunks) {
|
|
27
|
+
result.set(chunk, offset);
|
|
28
|
+
offset += chunk.byteLength;
|
|
29
|
+
}
|
|
30
|
+
return result.buffer;
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
signal?.removeEventListener('abort', cancel);
|
|
34
|
+
await reader.cancel().catch(() => { });
|
|
35
|
+
reader.releaseLock();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=bounded-body.js.map
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
12
12
|
import { pathMatches } from '@noodle-borg/knowledge/portable';
|
|
13
|
+
import { readBoundedBody } from './bounded-body.js';
|
|
13
14
|
import { extractPageContent } from './html-text.js';
|
|
14
15
|
import { parseOrigin, safeUrl } from './origin.js';
|
|
15
16
|
import { parseRobots } from './robots.js';
|
|
@@ -95,9 +96,7 @@ export class FirstPartySiteFetcher {
|
|
|
95
96
|
const response = await this.#fetch(url, { redirect: 'manual' });
|
|
96
97
|
if (response.status !== 200)
|
|
97
98
|
return undefined;
|
|
98
|
-
const bytes = await response
|
|
99
|
-
if (bytes.byteLength > maxPageBytes)
|
|
100
|
-
return undefined;
|
|
99
|
+
const bytes = await readBoundedBody(response, maxPageBytes);
|
|
101
100
|
return extractPageContent(response.headers.get('content-type') ?? '', bytes);
|
|
102
101
|
}
|
|
103
102
|
catch {
|
|
@@ -23,10 +23,18 @@ const NAMED_ENTITIES = {
|
|
|
23
23
|
};
|
|
24
24
|
function decodeEntities(text) {
|
|
25
25
|
return text
|
|
26
|
-
.replace(/&#x([0-9a-f]+);/gi, (_, hex) =>
|
|
27
|
-
.replace(/&#(\d+);/g, (_, dec) =>
|
|
26
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => safeCodePoint(Number.parseInt(hex, 16)))
|
|
27
|
+
.replace(/&#(\d+);/g, (_, dec) => safeCodePoint(Number.parseInt(dec, 10)))
|
|
28
28
|
.replace(/&([a-z]+);/gi, (match, name) => NAMED_ENTITIES[name.toLowerCase()] ?? match);
|
|
29
29
|
}
|
|
30
|
+
function safeCodePoint(value) {
|
|
31
|
+
return Number.isSafeInteger(value) &&
|
|
32
|
+
value > 0 &&
|
|
33
|
+
value <= 0x10ffff &&
|
|
34
|
+
!(value >= 0xd800 && value <= 0xdfff)
|
|
35
|
+
? String.fromCodePoint(value)
|
|
36
|
+
: '\uFFFD';
|
|
37
|
+
}
|
|
30
38
|
export function extractHtmlText(html) {
|
|
31
39
|
const withoutBlocks = html.replace(NON_CONTENT_BLOCKS, ' ');
|
|
32
40
|
const withBreaks = withoutBlocks.replace(BLOCK_BREAK, ' ');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { FirecrawlSiteFetcher, type FirecrawlSiteFetcherOptions } from './firecrawl.js';
|
|
2
2
|
export { FirstPartySiteFetcher, type FirstPartySiteFetcherOptions } from './first-party.js';
|
|
3
|
+
export { type PageReadRequest, PublicPageReader, type ReadPublicPage } from './page-reader.js';
|
|
3
4
|
export type { CrawlPage, SiteCrawlRequest, SiteFetcher } from './ports.js';
|
|
4
5
|
export { TavilySiteFetcher, type TavilySiteFetcherOptions } from './tavily.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface PageReadRequest {
|
|
2
|
+
readonly url: string;
|
|
3
|
+
readonly domains?: readonly string[];
|
|
4
|
+
readonly signal: AbortSignal;
|
|
5
|
+
readonly beforeRequest: () => void;
|
|
6
|
+
readonly maxBytes: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ReadPublicPage {
|
|
9
|
+
readonly url: string;
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly text: string;
|
|
12
|
+
readonly links: readonly {
|
|
13
|
+
readonly url: string;
|
|
14
|
+
readonly label?: string;
|
|
15
|
+
}[];
|
|
16
|
+
readonly retrievedAt: string;
|
|
17
|
+
}
|
|
18
|
+
/** Single-page acquisition, not a crawler. All transport collaborators are deployment-owned. */
|
|
19
|
+
export declare class PublicPageReader {
|
|
20
|
+
#private;
|
|
21
|
+
constructor(options?: {
|
|
22
|
+
readonly fetchImpl?: typeof fetch;
|
|
23
|
+
});
|
|
24
|
+
read(request: PageReadRequest): Promise<ReadPublicPage>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=page-reader.d.ts.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
2
|
+
import { CapabilityError, publicPageUrl, WEB_EXTRACT_LIMITS, } from '@noodle-borg/managed-capabilities';
|
|
3
|
+
import { readBoundedBody } from './bounded-body.js';
|
|
4
|
+
import { extractHtmlText, extractPageContent } from './html-text.js';
|
|
5
|
+
import { parseRobots } from './robots.js';
|
|
6
|
+
const PRODUCT_TOKEN = 'NoodleSeedBot';
|
|
7
|
+
const HEADERS = {
|
|
8
|
+
'user-agent': `${PRODUCT_TOKEN}/1.0 (bounded public-page extraction)`,
|
|
9
|
+
accept: 'text/html,text/plain;q=0.9',
|
|
10
|
+
'accept-encoding': 'identity',
|
|
11
|
+
};
|
|
12
|
+
/** Single-page acquisition, not a crawler. All transport collaborators are deployment-owned. */
|
|
13
|
+
export class PublicPageReader {
|
|
14
|
+
#fetch;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.#fetch = options.fetchImpl ?? ((url, init) => guardedFetch(new URL(String(url)), init));
|
|
17
|
+
}
|
|
18
|
+
async read(request) {
|
|
19
|
+
const signal = AbortSignal.any([
|
|
20
|
+
request.signal,
|
|
21
|
+
AbortSignal.timeout(WEB_EXTRACT_LIMITS.timeoutMs),
|
|
22
|
+
]);
|
|
23
|
+
let url = publicPageUrl(request.url, request.domains);
|
|
24
|
+
const checked = new Map();
|
|
25
|
+
try {
|
|
26
|
+
for (let redirects = 0; redirects <= WEB_EXTRACT_LIMITS.maxRedirects; redirects += 1) {
|
|
27
|
+
signal.throwIfAborted();
|
|
28
|
+
let robots = checked.get(url.origin);
|
|
29
|
+
if (robots === undefined) {
|
|
30
|
+
robots = await this.#robots(url, request, signal);
|
|
31
|
+
checked.set(url.origin, robots);
|
|
32
|
+
}
|
|
33
|
+
if (!robots.isAllowed(url.pathname))
|
|
34
|
+
throw new CapabilityError('capability_source_rejected');
|
|
35
|
+
const response = await this.#request(url, request, signal);
|
|
36
|
+
try {
|
|
37
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
38
|
+
const location = response.headers.get('location');
|
|
39
|
+
if (location === null || redirects === WEB_EXTRACT_LIMITS.maxRedirects) {
|
|
40
|
+
throw new CapabilityError('capability_source_rejected');
|
|
41
|
+
}
|
|
42
|
+
url = publicPageUrl(new URL(location, url).href, request.domains);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (response.status !== 200)
|
|
46
|
+
throw new CapabilityError('capability_source_rejected');
|
|
47
|
+
const mime = response.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase();
|
|
48
|
+
if (mime !== 'text/html')
|
|
49
|
+
throw new CapabilityError('capability_source_rejected');
|
|
50
|
+
const bytes = await readBoundedBody(response, Math.min(request.maxBytes, WEB_EXTRACT_LIMITS.maxPageBytes), signal);
|
|
51
|
+
signal.throwIfAborted();
|
|
52
|
+
const content = extractPageContent(mime, bytes);
|
|
53
|
+
if (content === undefined || content.text === '')
|
|
54
|
+
throw new CapabilityError('capability_source_rejected');
|
|
55
|
+
return {
|
|
56
|
+
url: url.href,
|
|
57
|
+
title: content.title.slice(0, 512),
|
|
58
|
+
text: content.text,
|
|
59
|
+
links: pageLinks(content.html, url, request.domains),
|
|
60
|
+
retrievedAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await response.body?.cancel().catch(() => { });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (signal.aborted)
|
|
70
|
+
throw new CapabilityError('capability_cancelled');
|
|
71
|
+
if (error instanceof CapabilityError)
|
|
72
|
+
throw error;
|
|
73
|
+
throw new CapabilityError('capability_source_rejected');
|
|
74
|
+
}
|
|
75
|
+
throw new CapabilityError('capability_source_rejected');
|
|
76
|
+
}
|
|
77
|
+
async #request(url, request, signal) {
|
|
78
|
+
signal.throwIfAborted();
|
|
79
|
+
request.beforeRequest();
|
|
80
|
+
return this.#fetch(url.href, {
|
|
81
|
+
method: 'GET',
|
|
82
|
+
redirect: 'manual',
|
|
83
|
+
credentials: 'omit',
|
|
84
|
+
headers: HEADERS,
|
|
85
|
+
signal,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async #robots(url, request, signal) {
|
|
89
|
+
const response = await this.#request(new URL('/robots.txt', url), request, signal);
|
|
90
|
+
try {
|
|
91
|
+
if (response.status === 404 || response.status === 410)
|
|
92
|
+
return parseRobots('', PRODUCT_TOKEN);
|
|
93
|
+
if (response.status !== 200)
|
|
94
|
+
throw new CapabilityError('capability_source_rejected');
|
|
95
|
+
const bytes = await readBoundedBody(response, WEB_EXTRACT_LIMITS.maxRobotsBytes, signal);
|
|
96
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
97
|
+
if (text.trimStart().startsWith('<') || text.includes('\0'))
|
|
98
|
+
throw new CapabilityError('capability_source_rejected');
|
|
99
|
+
return parseRobots(text, PRODUCT_TOKEN);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
await response.body?.cancel().catch(() => { });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function pageLinks(html, base, domains) {
|
|
107
|
+
const links = [];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
// Bounded HTML has no evaluator. Link labels and destinations are untrusted and are not fetched here.
|
|
110
|
+
for (const match of html.matchAll(/<a\b[^>]*?href\s*=\s*["']([^"']{1,2048})["'][^>]*>([\s\S]*?)<\/a\s*>/gi)) {
|
|
111
|
+
try {
|
|
112
|
+
const url = publicPageUrl(new URL(match[1] ?? '', base).href, domains);
|
|
113
|
+
if (url.origin !== base.origin || seen.has(url.href))
|
|
114
|
+
continue;
|
|
115
|
+
seen.add(url.href);
|
|
116
|
+
const label = extractHtmlText(match[2] ?? '').slice(0, 200);
|
|
117
|
+
links.push({ url: url.href, ...(label === '' ? {} : { label }) });
|
|
118
|
+
if (links.length === WEB_EXTRACT_LIMITS.maxLinks)
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* Unsafe or out-of-policy links do not become candidate sources. */
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return links;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=page-reader.js.map
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* needed, verify the dependency license first (root rules).
|
|
2
|
+
* Bounded robots groups and longest-match rules. An explicit product token selects its exact
|
|
3
|
+
* groups before wildcard groups; omitted token retains the crawler's wildcard-group behavior.
|
|
4
|
+
* Supports percent-normalized paths, wildcard and end-anchor matching without a backtracking regex.
|
|
6
5
|
*/
|
|
7
|
-
export function parseRobots(text) {
|
|
8
|
-
const
|
|
6
|
+
export function parseRobots(text, productToken = '*') {
|
|
7
|
+
const groups = [];
|
|
9
8
|
const sitemaps = [];
|
|
10
|
-
let
|
|
11
|
-
|
|
9
|
+
let group = { agents: [], rules: [] };
|
|
10
|
+
groups.push(group);
|
|
11
|
+
let ruleCount = 0;
|
|
12
12
|
for (const rawLine of text.split(/\r?\n/)) {
|
|
13
13
|
const line = rawLine.replace(/#.*$/, '').trim();
|
|
14
14
|
if (line === '')
|
|
@@ -24,32 +24,31 @@ export function parseRobots(text) {
|
|
|
24
24
|
continue;
|
|
25
25
|
}
|
|
26
26
|
if (field === 'user-agent') {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
inStarGroup = false;
|
|
31
|
-
groupHasAgents = false;
|
|
27
|
+
if (group.rules.length > 0) {
|
|
28
|
+
group = { agents: [], rules: [] };
|
|
29
|
+
groups.push(group);
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
inStarGroup = true;
|
|
31
|
+
group.agents.push(value.toLowerCase());
|
|
35
32
|
continue;
|
|
36
33
|
}
|
|
37
34
|
if (field === 'allow' || field === 'disallow') {
|
|
38
|
-
|
|
39
|
-
if (!
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
continue;
|
|
44
|
-
rules.push({ allow: field === 'allow', path: value });
|
|
35
|
+
ruleCount += 1;
|
|
36
|
+
if (ruleCount > 4096 || value.length > 2048 || (value !== '' && !value.startsWith('/'))) {
|
|
37
|
+
throw new Error('robots_policy_invalid');
|
|
38
|
+
}
|
|
39
|
+
group.rules.push({ allow: field === 'allow', path: normalizePath(value) });
|
|
45
40
|
}
|
|
46
41
|
}
|
|
42
|
+
const exact = groups.filter((entry) => entry.agents.includes(productToken.toLowerCase()));
|
|
43
|
+
const selected = exact.length > 0 ? exact : groups.filter((entry) => entry.agents.includes('*'));
|
|
44
|
+
const rules = selected.flatMap((entry) => entry.rules).filter((rule) => rule.path !== '');
|
|
47
45
|
return {
|
|
48
46
|
sitemaps,
|
|
49
47
|
isAllowed(path) {
|
|
48
|
+
const normalized = normalizePath(path);
|
|
50
49
|
let best;
|
|
51
50
|
for (const rule of rules) {
|
|
52
|
-
if (!
|
|
51
|
+
if (!matches(normalized, rule.path))
|
|
53
52
|
continue;
|
|
54
53
|
if (best === undefined ||
|
|
55
54
|
rule.path.length > best.path.length ||
|
|
@@ -61,4 +60,30 @@ export function parseRobots(text) {
|
|
|
61
60
|
},
|
|
62
61
|
};
|
|
63
62
|
}
|
|
63
|
+
function normalizePath(path) {
|
|
64
|
+
return path.replace(/%[0-9a-f]{2}|\P{ASCII}/giu, (part) => {
|
|
65
|
+
if (!part.startsWith('%'))
|
|
66
|
+
return encodeURIComponent(part);
|
|
67
|
+
const decoded = String.fromCharCode(Number.parseInt(part.slice(1), 16));
|
|
68
|
+
return /^[a-z0-9._~-]$/i.test(decoded) ? decoded : part.toUpperCase();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function matches(path, pattern) {
|
|
72
|
+
const anchored = pattern.endsWith('$');
|
|
73
|
+
const parts = (anchored ? pattern.slice(0, -1) : pattern).split('*');
|
|
74
|
+
const first = parts[0] ?? '';
|
|
75
|
+
if (!path.startsWith(first))
|
|
76
|
+
return false;
|
|
77
|
+
let position = first.length;
|
|
78
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
79
|
+
const part = parts[i] ?? '';
|
|
80
|
+
if (anchored && i === parts.length - 1)
|
|
81
|
+
return path.endsWith(part) && path.length - part.length >= position;
|
|
82
|
+
const next = path.indexOf(part, position);
|
|
83
|
+
if (next === -1)
|
|
84
|
+
return false;
|
|
85
|
+
position = next + part.length;
|
|
86
|
+
}
|
|
87
|
+
return !anchored || position === path.length;
|
|
88
|
+
}
|
|
64
89
|
//# sourceMappingURL=robots.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { WebPolicy } from './contracts.js';
|
|
3
|
+
declare const snapshotSchema: z.ZodObject<{
|
|
4
|
+
startedAt: z.ZodNullable<z.ZodNumber>;
|
|
5
|
+
timeout: z.ZodNumber;
|
|
6
|
+
maxCalls: z.ZodNumber;
|
|
7
|
+
maxUrls: z.ZodNumber;
|
|
8
|
+
calls: z.ZodNumber;
|
|
9
|
+
attempts: z.ZodNumber;
|
|
10
|
+
bytes: z.ZodNumber;
|
|
11
|
+
urls: z.ZodArray<z.ZodString>;
|
|
12
|
+
}, z.core.$strict>;
|
|
13
|
+
export type CapabilityBudgetSnapshot = z.infer<typeof snapshotSchema>;
|
|
14
|
+
/** Trusted turn/invocation state. Never reconstructed from client arguments or stored in an artifact. */
|
|
15
|
+
export declare class CapabilityBudget {
|
|
16
|
+
#private;
|
|
17
|
+
constructor(snapshot?: CapabilityBudgetSnapshot);
|
|
18
|
+
/** Only for server-held continuations; no source URLs or content are retained. */
|
|
19
|
+
snapshot(): CapabilityBudgetSnapshot;
|
|
20
|
+
reserve(urls: readonly string[], policy: WebPolicy): AbortSignal;
|
|
21
|
+
beforeRequest(): void;
|
|
22
|
+
text(value: string, maxBytes: number): {
|
|
23
|
+
text: string;
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
};
|
|
26
|
+
withSlot<T>(signal: AbortSignal, run: () => Promise<T>): Promise<T>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { CapabilityError } from './errors.js';
|
|
4
|
+
import { WEB_EXTRACT_LIMITS as limits } from './limits.js';
|
|
5
|
+
const snapshotSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
startedAt: z.number().int().nonnegative().nullable(),
|
|
8
|
+
timeout: z.number().int().min(100).max(limits.timeoutMs),
|
|
9
|
+
maxCalls: z.number().int().min(1).max(limits.maxCalls),
|
|
10
|
+
maxUrls: z.number().int().min(1).max(limits.maxUrls),
|
|
11
|
+
calls: z.number().int().min(0).max(limits.maxCalls),
|
|
12
|
+
attempts: z.number().int().min(0).max(limits.maxHttpAttempts),
|
|
13
|
+
bytes: z.number().int().min(0).max(limits.maxTurnTextBytes),
|
|
14
|
+
urls: z.array(z.string().regex(/^[a-f0-9]{64}$/)).max(limits.maxUrls),
|
|
15
|
+
})
|
|
16
|
+
.strict();
|
|
17
|
+
const urlKey = (url) => createHash('sha256').update(url).digest('hex');
|
|
18
|
+
/** Trusted turn/invocation state. Never reconstructed from client arguments or stored in an artifact. */
|
|
19
|
+
export class CapabilityBudget {
|
|
20
|
+
#startedAt;
|
|
21
|
+
#timeout = limits.timeoutMs;
|
|
22
|
+
#maxCalls = limits.maxCalls;
|
|
23
|
+
#maxUrls = limits.maxUrls;
|
|
24
|
+
#calls = 0;
|
|
25
|
+
#attempts = 0;
|
|
26
|
+
#bytes = 0;
|
|
27
|
+
#active = 0;
|
|
28
|
+
#urls = new Set();
|
|
29
|
+
#waiters = new Set();
|
|
30
|
+
constructor(snapshot) {
|
|
31
|
+
if (snapshot === undefined)
|
|
32
|
+
return;
|
|
33
|
+
const parsed = snapshotSchema.parse(snapshot);
|
|
34
|
+
this.#startedAt = parsed.startedAt ?? undefined;
|
|
35
|
+
this.#timeout = parsed.timeout;
|
|
36
|
+
this.#maxCalls = parsed.maxCalls;
|
|
37
|
+
this.#maxUrls = parsed.maxUrls;
|
|
38
|
+
this.#calls = parsed.calls;
|
|
39
|
+
this.#attempts = parsed.attempts;
|
|
40
|
+
this.#bytes = parsed.bytes;
|
|
41
|
+
for (const key of parsed.urls)
|
|
42
|
+
this.#urls.add(key);
|
|
43
|
+
}
|
|
44
|
+
/** Only for server-held continuations; no source URLs or content are retained. */
|
|
45
|
+
snapshot() {
|
|
46
|
+
return {
|
|
47
|
+
startedAt: this.#startedAt ?? null,
|
|
48
|
+
timeout: this.#timeout,
|
|
49
|
+
maxCalls: this.#maxCalls,
|
|
50
|
+
maxUrls: this.#maxUrls,
|
|
51
|
+
calls: this.#calls,
|
|
52
|
+
attempts: this.#attempts,
|
|
53
|
+
bytes: this.#bytes,
|
|
54
|
+
urls: [...this.#urls],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
reserve(urls, policy) {
|
|
58
|
+
this.#startedAt ??= Date.now();
|
|
59
|
+
this.#timeout = Math.min(this.#timeout, policy.timeoutMs ?? limits.timeoutMs);
|
|
60
|
+
this.#maxCalls = Math.min(this.#maxCalls, policy.maxCalls ?? limits.maxCalls);
|
|
61
|
+
this.#maxUrls = Math.min(this.#maxUrls, policy.maxUrls ?? limits.maxUrls);
|
|
62
|
+
const next = new Set([...this.#urls, ...urls.map(urlKey)]);
|
|
63
|
+
if (this.#calls >= this.#maxCalls || next.size > this.#maxUrls)
|
|
64
|
+
this.#exhausted();
|
|
65
|
+
this.#checkTime();
|
|
66
|
+
this.#calls += 1;
|
|
67
|
+
for (const url of urls)
|
|
68
|
+
this.#urls.add(urlKey(url));
|
|
69
|
+
return AbortSignal.timeout(Math.max(1, this.#startedAt + this.#timeout - Date.now()));
|
|
70
|
+
}
|
|
71
|
+
beforeRequest() {
|
|
72
|
+
this.#checkTime();
|
|
73
|
+
if (this.#attempts >= limits.maxHttpAttempts)
|
|
74
|
+
this.#exhausted();
|
|
75
|
+
this.#attempts += 1;
|
|
76
|
+
}
|
|
77
|
+
text(value, maxBytes) {
|
|
78
|
+
const bytes = Buffer.from(value, 'utf8');
|
|
79
|
+
let length = Math.min(bytes.length, maxBytes, limits.maxTurnTextBytes - this.#bytes);
|
|
80
|
+
// Drop any incomplete final code point rather than introducing a replacement character.
|
|
81
|
+
if (length < bytes.length)
|
|
82
|
+
while (length > 0 && ((bytes[length] ?? 0) & 0xc0) === 0x80)
|
|
83
|
+
length -= 1;
|
|
84
|
+
if (length === 0)
|
|
85
|
+
this.#exhausted();
|
|
86
|
+
this.#bytes += length;
|
|
87
|
+
return { text: bytes.subarray(0, length).toString('utf8'), truncated: length < bytes.length };
|
|
88
|
+
}
|
|
89
|
+
async withSlot(signal, run) {
|
|
90
|
+
while (this.#active >= limits.maxConcurrency) {
|
|
91
|
+
signal.throwIfAborted();
|
|
92
|
+
await new Promise((resolve) => {
|
|
93
|
+
const wake = () => {
|
|
94
|
+
this.#waiters.delete(wake);
|
|
95
|
+
signal.removeEventListener('abort', wake);
|
|
96
|
+
resolve();
|
|
97
|
+
};
|
|
98
|
+
this.#waiters.add(wake);
|
|
99
|
+
signal.addEventListener('abort', wake, { once: true });
|
|
100
|
+
if (signal.aborted)
|
|
101
|
+
wake();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
signal.throwIfAborted();
|
|
105
|
+
this.#checkTime();
|
|
106
|
+
this.#active += 1;
|
|
107
|
+
try {
|
|
108
|
+
return await run();
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
this.#active -= 1;
|
|
112
|
+
for (const wake of [...this.#waiters])
|
|
113
|
+
wake();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
#checkTime() {
|
|
117
|
+
if (this.#startedAt !== undefined && Date.now() >= this.#startedAt + this.#timeout)
|
|
118
|
+
this.#exhausted();
|
|
119
|
+
}
|
|
120
|
+
#exhausted() {
|
|
121
|
+
throw new CapabilityError('capability_budget_exhausted');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=budget.js.map
|