blob: b516d69e81864f634ab0e69beeaf5e6ad945f067 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
requireEntityId,
requireExactRecord,
requireShapedRecord,
requireString,
} from './codec.js';
import { invalidProtocolFrame } from './errors.js';
import { defineOperation } from './operation-spec.js';
// Official ACP Registry, pinned to the distribution verified for this integration.
export const ANTIGRAVITY_ACP_RELEASE = {
version: '1.1.1',
url: 'https://dl.google.com/agy-extensions/releases/macos/agy-acp-server-agy_acp_server_1.1.1-darwin-arm64.zip',
archiveBytes: 316_014_828,
sha256: 'fdfa915652cdb7ba8085cc8fffed072cbe009251aa2c951aabdda07a8c28a189',
} as const;
export type ExternalAgentSetupAction = 'check' | 'login' | 'install';
export type ExternalAgentSetupPhase =
| 'downloading'
| 'installing'
| 'connecting'
| 'awaiting_authorization'
| 'cancelling'
| 'succeeded'
| 'failed'
| 'cancelled';
export const EXTERNAL_AGENT_SETUP_FAILURES = [
'download_failed',
'integrity_failed',
'installation_failed',
'executable_unavailable',
'helper_unavailable',
'connection_failed',
'authentication_unavailable',
'authentication_failed',
'account_ineligible',
'browser_failed',
'timed_out',
'cleanup_failed',
] as const;
export type ExternalAgentSetupFailure = (typeof EXTERNAL_AGENT_SETUP_FAILURES)[number];
export interface ExternalAgentSetupStart {
readonly attemptId: string;
readonly action: ExternalAgentSetupAction;
/** Comparison only: the Host launches the path read from RuntimePolicy. */
readonly expectedExecutable: string;
}
export interface ExternalAgentSetupAttempt {
readonly attemptId: string;
}
export interface ExternalAgentSetupProjection extends ExternalAgentSetupStart {
readonly phase: ExternalAgentSetupPhase;
readonly failure?: ExternalAgentSetupFailure;
readonly installedExecutable?: string;
readonly downloadPercent?: number;
}
const errors = [
'host_not_ready',
'host_draining',
'operation_unavailable',
'invalid_request',
'internal_failure',
'operation_conflict',
'not_found',
] as const;
const common = {
availability: 'ready' as const,
errors,
decodeOutput: decodeExternalAgentSetupProjection,
assertOutputForInput(input: ExternalAgentSetupAttempt, output: ExternalAgentSetupProjection) {
if (input.attemptId !== output.attemptId)
throw invalidProtocolFrame('Setup attempt changed identity');
},
};
export const EXTERNAL_AGENT_SETUP_OPERATION_SPECS = {
'external_agents.setup.start': defineOperation<
ExternalAgentSetupStart,
ExternalAgentSetupProjection,
(typeof errors)[number]
>({
...common,
mode: 'command',
decodeInput: decodeExternalAgentSetupStart,
assertOutputForInput(input, output) {
common.assertOutputForInput(input, output);
if (input.action !== output.action || input.expectedExecutable !== output.expectedExecutable)
throw invalidProtocolFrame('Setup changed configuration');
},
}),
'external_agents.setup.query': defineOperation<
ExternalAgentSetupAttempt,
ExternalAgentSetupProjection,
(typeof errors)[number]
>({
...common,
mode: 'query',
decodeInput: decodeExternalAgentSetupAttempt,
}),
'external_agents.setup.cancel': defineOperation<
ExternalAgentSetupAttempt,
ExternalAgentSetupProjection,
(typeof errors)[number]
>({
...common,
mode: 'control',
decodeInput: decodeExternalAgentSetupAttempt,
}),
} as const;
export function decodeExternalAgentSetupStart(value: unknown): ExternalAgentSetupStart {
const item = requireExactRecord(value, 'external agent setup', [
'attemptId',
'action',
'expectedExecutable',
]);
return startFields(item);
}
function startFields(item: Record<string, unknown>): ExternalAgentSetupStart {
if (item.action !== 'check' && item.action !== 'login' && item.action !== 'install')
throw invalidProtocolFrame('Invalid setup action');
return {
attemptId: requireEntityId(item.attemptId, 'attemptId'),
action: item.action,
expectedExecutable:
item.action === 'install' && item.expectedExecutable === ''
? ''
: requireString(item.expectedExecutable, 'expectedExecutable', 4096),
};
}
export function decodeExternalAgentSetupAttempt(value: unknown): ExternalAgentSetupAttempt {
const item = requireExactRecord(value, 'setup attempt', ['attemptId']);
return { attemptId: requireEntityId(item.attemptId, 'attemptId') };
}
export function decodeExternalAgentSetupProjection(value: unknown): ExternalAgentSetupProjection {
const item = requireShapedRecord(
value,
'setup projection',
['attemptId', 'action', 'expectedExecutable', 'phase'],
['failure', 'installedExecutable', 'downloadPercent'],
);
if (
![
'downloading',
'installing',
'connecting',
'awaiting_authorization',
'cancelling',
'succeeded',
'failed',
'cancelled',
].includes(item.phase as string)
)
throw invalidProtocolFrame('Invalid setup phase');
if (
item.phase === 'failed'
? !EXTERNAL_AGENT_SETUP_FAILURES.includes(item.failure as ExternalAgentSetupFailure)
: item.failure !== undefined
)
throw invalidProtocolFrame('Invalid setup failure');
if ((item.phase === 'downloading' || item.phase === 'installing') && item.action !== 'install')
throw invalidProtocolFrame('Invalid installation phase');
if (
item.downloadPercent !== undefined &&
(item.action !== 'install' ||
!Number.isInteger(item.downloadPercent) ||
(item.downloadPercent as number) < 0 ||
(item.downloadPercent as number) > 100)
)
throw invalidProtocolFrame('Invalid download progress');
if (item.action === 'install' && item.phase === 'succeeded') {
const path = requireString(item.installedExecutable, 'installedExecutable', 4096);
if (!path.startsWith('/') || /[\x00-\x1f]/u.test(path))
throw invalidProtocolFrame('Invalid installed path');
} else if (item.installedExecutable !== undefined)
throw invalidProtocolFrame('Unexpected installed path');
return {
...startFields(item),
...(item.installedExecutable !== undefined
? { installedExecutable: item.installedExecutable as string }
: {}),
...(item.downloadPercent !== undefined
? { downloadPercent: item.downloadPercent as number }
: {}),
phase: item.phase as ExternalAgentSetupPhase,
...(item.phase === 'failed' ? { failure: item.failure as ExternalAgentSetupFailure } : {}),
};
}